1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2009 Benny Malengier 5# Copyright (C) 2011 Tim G L Lyons 6# 7# This program is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation; either version 2 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program; if not, write to the Free Software 19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20# 21 22""" 23This module provides the base class for plugin registration. 24It provides an object containing data about the plugin (version, filename, ...) 25and a register for the data of all plugins . 26""" 27#------------------------------------------------------------------------- 28# 29# Standard Python modules 30# 31#------------------------------------------------------------------------- 32import os 33import sys 34import re 35import traceback 36 37#------------------------------------------------------------------------- 38# 39# Gramps modules 40# 41#------------------------------------------------------------------------- 42from ...version import VERSION as GRAMPSVERSION, VERSION_TUPLE 43from ..const import IMAGE_DIR 44from ..const import GRAMPS_LOCALE as glocale 45_ = glocale.translation.gettext 46import logging 47LOG = logging.getLogger('._manager') 48 49#------------------------------------------------------------------------- 50# 51# PluginData 52# 53#------------------------------------------------------------------------- 54 55#a plugin is stable or unstable 56STABLE = 0 57UNSTABLE = 1 58STATUS = [STABLE, UNSTABLE] 59STATUSTEXT = {STABLE: _('Stable'), UNSTABLE: _('Unstable')} 60#possible plugin types 61REPORT = 0 62QUICKREPORT = 1 # deprecated 63QUICKVIEW = 1 64TOOL = 2 65IMPORT = 3 66EXPORT = 4 67DOCGEN = 5 68GENERAL = 6 69MAPSERVICE = 7 70VIEW = 8 71RELCALC = 9 72GRAMPLET = 10 73SIDEBAR = 11 74DATABASE = 12 75RULE = 13 76PTYPE = [REPORT, QUICKREPORT, TOOL, IMPORT, EXPORT, DOCGEN, GENERAL, 77 MAPSERVICE, VIEW, RELCALC, GRAMPLET, SIDEBAR, DATABASE, RULE] 78PTYPE_STR = { 79 REPORT: _('Report') , 80 QUICKREPORT: _('Quickreport'), 81 TOOL: _('Tool'), 82 IMPORT: _('Importer'), 83 EXPORT: _('Exporter'), 84 DOCGEN: _('Doc creator'), 85 GENERAL: _('Plugin lib'), 86 MAPSERVICE: _('Map service'), 87 VIEW: _('Gramps View'), 88 RELCALC: _('Relationships'), 89 GRAMPLET: _('Gramplet'), 90 SIDEBAR: _('Sidebar'), 91 DATABASE: _('Database'), 92 RULE: _('Rule') 93 } 94 95#possible report categories 96CATEGORY_TEXT = 0 97CATEGORY_DRAW = 1 98CATEGORY_CODE = 2 99CATEGORY_WEB = 3 100CATEGORY_BOOK = 4 101CATEGORY_GRAPHVIZ = 5 102CATEGORY_TREE = 6 103REPORT_CAT = [ CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_CODE, 104 CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ, 105 CATEGORY_TREE] 106#possible tool categories 107TOOL_DEBUG = -1 108TOOL_ANAL = 0 109TOOL_DBPROC = 1 110TOOL_DBFIX = 2 111TOOL_REVCTL = 3 112TOOL_UTILS = 4 113TOOL_CAT = [ TOOL_DEBUG, TOOL_ANAL, TOOL_DBPROC, TOOL_DBFIX, TOOL_REVCTL, 114 TOOL_UTILS] 115 116#possible quickreport categories 117CATEGORY_QR_MISC = -1 118CATEGORY_QR_PERSON = 0 119CATEGORY_QR_FAMILY = 1 120CATEGORY_QR_EVENT = 2 121CATEGORY_QR_SOURCE = 3 122CATEGORY_QR_PLACE = 4 123CATEGORY_QR_REPOSITORY = 5 124CATEGORY_QR_NOTE = 6 125CATEGORY_QR_DATE = 7 126CATEGORY_QR_MEDIA = 8 127CATEGORY_QR_CITATION = 9 128CATEGORY_QR_SOURCE_OR_CITATION = 10 129 130# Modes for generating reports 131REPORT_MODE_GUI = 1 # Standalone report using GUI 132REPORT_MODE_BKI = 2 # Book Item interface using GUI 133REPORT_MODE_CLI = 4 # Command line interface (CLI) 134REPORT_MODES = [REPORT_MODE_GUI, REPORT_MODE_BKI, REPORT_MODE_CLI] 135 136# Modes for running tools 137TOOL_MODE_GUI = 1 # Standard tool using GUI 138TOOL_MODE_CLI = 2 # Command line interface (CLI) 139TOOL_MODES = [TOOL_MODE_GUI, TOOL_MODE_CLI] 140 141# possible view orders 142START = 1 143END = 2 144 145#------------------------------------------------------------------------- 146# 147# Functions and classes 148# 149#------------------------------------------------------------------------- 150def myint(s): 151 """ 152 Protected version of int() 153 """ 154 try: 155 v = int(s) 156 except: 157 v = s 158 return v 159 160def version(sversion): 161 """ 162 Return the tuple version of a string version. 163 """ 164 return tuple([myint(x or "0") for x in (sversion + "..").split(".")]) 165 166def valid_plugin_version(plugin_version_string): 167 """ 168 Checks to see if string is a valid version string for this version 169 of Gramps. 170 """ 171 if not isinstance(plugin_version_string, str): return False 172 dots = plugin_version_string.count(".") 173 if dots == 1: 174 plugin_version = tuple(map(int, plugin_version_string.split(".", 1))) 175 return plugin_version == VERSION_TUPLE[:2] 176 elif dots == 2: 177 plugin_version = tuple(map(int, plugin_version_string.split(".", 2))) 178 return (plugin_version[:2] == VERSION_TUPLE[:2] and 179 plugin_version <= VERSION_TUPLE) 180 return False 181 182class PluginData: 183 """ 184 This is the base class for all plugin data objects. 185 The workflow is: 186 187 1. plugin manager reads all register files, and stores plugin data 188 objects in a plugin register 189 2. when plugin is needed, the plugin register creates the plugin, and 190 the manager stores this, after which it can be executed. 191 192 Attributes present for all plugins 193 194 .. attribute:: id 195 A unique identifier for the plugin. This is eg used to store the plugin 196 settings. MUST be in ASCII, with only "_- ().,'" special characters. 197 .. attribute:: name 198 A friendly name to call this plugin (normally translated) 199 .. attribute:: name_accell 200 A friendly name to call this plugin (normally translated), with an 201 accellerator present (eg '_Descendant report', with D to be accellerator 202 key 203 .. attribute:: description 204 A friendly description of what the plugin does 205 .. attribute:: version 206 The version of the plugin 207 .. attribute:: status 208 The status of the plugin, STABLE or UNSTABLE 209 UNSTABLE is only visible in development code, not in release 210 .. attribute:: fname 211 The python file where the plugin implementation can be found 212 .. attribute:: fpath 213 The python path where the plugin implementation can be found 214 .. attribute:: ptype 215 The plugin type. One of REPORT , QUICKREPORT, TOOL, IMPORT, 216 EXPORT, DOCGEN, GENERAL, MAPSERVICE, VIEW, GRAMPLET, DATABASE, RULE 217 .. attribute:: authors 218 List of authors of the plugin, default=[] 219 .. attribute:: authors_email 220 List of emails of the authors of the plugin, default=[] 221 .. attribute:: supported 222 Bool value indicating if the plugin is still supported, default=True 223 .. attribute:: load_on_reg 224 bool value, if True, the plugin is loaded on Gramps startup. Some 225 plugins. Only set this value if for testing you want the plugin to be 226 loaded immediately on startup. default=False 227 .. attribute: icons 228 New stock icons to register. A list of tuples (stock_id, icon_label), 229 eg: 230 [('gramps_myplugin', _('My Plugin')), 231 ('gramps_myplugin_open', _('Open Plugin')] 232 The icon directory must contain the directories scalable, 48x48, 22x22 233 and 16x16 with the icons, eg: 234 scalable/gramps_myplugin.svg 235 48x48/gramps_myplugin.png 236 22x22/gramps_myplugin.png 237 .. attribute: icondir 238 The directory to use for the icons. If icondir is not set or None, it 239 reverts to the plugindirectory itself. 240 241 Attributes for RELCALC plugins: 242 243 .. attribute:: relcalcclass 244 The class in the module that is the relationcalc class 245 .. attribute:: lang_list 246 List of languages this plugin handles 247 248 Attributes for REPORT plugins: 249 250 .. attribute:: require_active 251 Bool, If the reports requries an active person to be set or not 252 .. attribute:: reportclass 253 The class in the module that is the report class 254 .. attribute:: report_modes 255 The report modes: list of REPORT_MODE_GUI ,REPORT_MODE_BKI,REPORT_MODE_CLI 256 257 Attributes for REPORT and TOOL and QUICKREPORT and VIEW plugins 258 259 .. attribute:: category 260 Or the report category the plugin belongs to, default=CATEGORY_TEXT 261 or the tool category a plugin belongs to, default=TOOL_UTILS 262 or the quickreport category a plugin belongs to, default=CATEGORY_QR_PERSON 263 or the view category a plugin belongs to, 264 default=("Miscellaneous", _("Miscellaneous")) 265 266 Attributes for REPORT and TOOL and DOCGEN plugins 267 268 .. attribute:: optionclass 269 The class in the module that is the option class 270 271 Attributes for TOOL plugins 272 273 .. attribute:: toolclass 274 The class in the module that is the tool class 275 .. attribute:: tool_modes 276 The tool modes: list of TOOL_MODE_GUI, TOOL_MODE_CLI 277 278 Attributes for DOCGEN plugins 279 280 .. attribute :: docclass 281 The class in the module that is the BaseDoc defined 282 .. attribute :: paper 283 bool, Indicates whether the plugin uses paper or not, default=True 284 .. attribute :: style 285 bool, Indicates whether the plugin uses styles or not, default=True 286 287 Attribute for DOCGEN, EXPORT plugins 288 289 .. attribute :: extension 290 str, The file extension to use for output produced by the docgen/export, 291 default='' 292 293 Attributes for QUICKREPORT plugins 294 295 .. attribute:: runfunc 296 The function that executes the quick report 297 298 Attributes for MAPSERVICE plugins 299 300 .. attribute:: mapservice 301 The class in the module that is a mapservice 302 303 Attributes for EXPORT plugins 304 305 .. attribute:: export_function 306 Function that produces the export 307 .. attribute:: export_options 308 Class to set options 309 .. attribute:: export_options_title 310 Title for the option page 311 312 Attributes for IMPORT plugins 313 314 .. attribute:: import_function 315 Function that starts an import 316 317 Attributes for GRAMPLET plugins 318 319 .. attribute:: gramplet 320 The function or class that defines the gramplet. 321 .. attribute:: height 322 The height the gramplet should have in a column on GrampletView, 323 default = 200 324 .. attribute:: detached_height 325 The height the gramplet should have detached, default 300 326 .. attribute:: detached_width 327 The width the gramplet should have detached, default 400 328 .. attribute:: expand 329 If the attributed should be expanded on start, default False 330 .. attribute:: gramplet_title 331 Title to use for the gramplet, default = _('Gramplet') 332 .. attribute:: navtypes 333 Navigation types that the gramplet is appropriate for, default = [] 334 .. attribute:: help_url 335 The URL where documentation for the URL can be found 336 337 Attributes for VIEW plugins 338 339 .. attribute:: viewclass 340 A class of type ViewCreator that holds the needed info of the 341 view to be created: icon, viewclass that derives from pageview, ... 342 .. attribute:: stock_icon 343 The icon in the toolbar or sidebar used to select the view 344 345 Attributes for SIDEBAR plugins 346 347 .. attribute:: sidebarclass 348 The class that defines the sidebar. 349 .. attribute:: menu_label 350 A label to use on the seltion menu. 351 352 Attributes for VIEW and SIDEBAR plugins 353 354 .. attribute:: order 355 order can be START or END. Default is END. For END, on registering, 356 the plugin is appended to the list of plugins. If START, then the 357 plugin is prepended. Only set START if you want a plugin to be the 358 first in the order of plugins 359 360 Attributes for DATABASE plugins 361 362 .. attribute:: databaseclass 363 The class in the module that is the database class 364 .. attribute:: reset_system 365 Boolean to indicate that the system (sys.modules) should 366 be reset. 367 368 Attributes for RULE plugins 369 370 .. attribute:: namespace 371 The class (Person, Event, Media, etc.) the rule applies to. 372 .. attribute:: ruleclass 373 The exact class name of the rule; ex: HasSourceParameter 374 """ 375 376 def __init__(self): 377 #read/write attribute 378 self.directory = None 379 #base attributes 380 self._id = None 381 self._name = None 382 self._name_accell = None 383 self._version = None 384 self._gramps_target_version = None 385 self._description = None 386 self._status = UNSTABLE 387 self._fname = None 388 self._fpath = None 389 self._ptype = None 390 self._authors = [] 391 self._authors_email = [] 392 self._supported = True 393 self._load_on_reg = False 394 self._icons = [] 395 self._icondir = None 396 self._depends_on = [] 397 self._include_in_listing = True 398 #derived var 399 self.mod_name = None 400 #RELCALC attr 401 self._relcalcclass = None 402 self._lang_list = None 403 #REPORT attr 404 self._reportclass = None 405 self._require_active = True 406 self._report_modes = [REPORT_MODE_GUI] 407 #REPORT and TOOL and GENERAL attr 408 self._category = None 409 #REPORT and TOOL attr 410 self._optionclass = None 411 #TOOL attr 412 self._toolclass = None 413 self._tool_modes = [TOOL_MODE_GUI] 414 #DOCGEN attr 415 self._paper = True 416 self._style = True 417 self._extension = '' 418 #QUICKREPORT attr 419 self._runfunc = None 420 #MAPSERVICE attr 421 self._mapservice = None 422 #EXPORT attr 423 self._export_function = None 424 self._export_options = None 425 self._export_options_title = '' 426 #IMPORT attr 427 self._import_function = None 428 #GRAMPLET attr 429 self._gramplet = None 430 self._height = 200 431 self._detached_height = 300 432 self._detached_width = 400 433 self._expand = False 434 self._gramplet_title = _('Gramplet') 435 self._navtypes = [] 436 self._orientation = None 437 self._help_url = None 438 #VIEW attr 439 self._viewclass = None 440 self._stock_icon = None 441 #SIDEBAR attr 442 self._sidebarclass = None 443 self._menu_label = '' 444 #VIEW and SIDEBAR attr 445 self._order = END 446 #DATABASE attr 447 self._databaseclass = None 448 self._reset_system = False 449 #GENERAL attr 450 self._data = [] 451 self._process = None 452 #RULE attr 453 self._ruleclass = None 454 self._namespace = None 455 456 def _set_id(self, id): 457 self._id = id 458 459 def _get_id(self): 460 return self._id 461 462 def _set_name(self, name): 463 self._name = name 464 465 def _get_name(self): 466 return self._name 467 468 def _set_name_accell(self, name): 469 self._name_accell = name 470 471 def _get_name_accell(self): 472 if self._name_accell is None: 473 return self._name 474 else: 475 return self._name_accell 476 477 def _set_description(self, description): 478 self._description = description 479 480 def _get_description(self): 481 return self._description 482 483 def _set_version(self, version): 484 self._version = version 485 486 def _get_version(self): 487 return self._version 488 489 def _set_gramps_target_version(self, version): 490 self._gramps_target_version = version 491 492 def _get_gramps_target_version(self): 493 return self._gramps_target_version 494 495 def _set_status(self, status): 496 if status not in STATUS: 497 raise ValueError('plugin status cannot be %s' % str(status)) 498 self._status = status 499 500 def _get_status(self): 501 return self._status 502 503 def _set_fname(self, fname): 504 self._fname = fname 505 506 def _get_fname(self): 507 return self._fname 508 509 def _set_fpath(self, fpath): 510 self._fpath = fpath 511 512 def _get_fpath(self): 513 return self._fpath 514 515 def _set_ptype(self, ptype): 516 if ptype not in PTYPE: 517 raise ValueError('Plugin type cannot be %s' % str(ptype)) 518 elif self._ptype is not None: 519 raise ValueError('Plugin type may not be changed') 520 self._ptype = ptype 521 if self._ptype == REPORT: 522 self._category = CATEGORY_TEXT 523 elif self._ptype == TOOL: 524 self._category = TOOL_UTILS 525 elif self._ptype == QUICKREPORT: 526 self._category = CATEGORY_QR_PERSON 527 elif self._ptype == VIEW: 528 self._category = ("Miscellaneous", _("Miscellaneous")) 529 #if self._ptype == DOCGEN: 530 # self._load_on_reg = True 531 532 def _get_ptype(self): 533 return self._ptype 534 535 def _set_authors(self, authors): 536 if not authors or not isinstance(authors, list): 537 return 538 self._authors = authors 539 540 def _get_authors(self): 541 return self._authors 542 543 def _set_authors_email(self, authors_email): 544 if not authors_email or not isinstance(authors_email, list): 545 return 546 self._authors_email = authors_email 547 548 def _get_authors_email(self): 549 return self._authors_email 550 551 def _set_supported(self, supported): 552 if not isinstance(supported, bool): 553 raise ValueError('Plugin must have supported=True or False') 554 self._supported = supported 555 556 def _get_supported(self): 557 return self._supported 558 559 def _set_load_on_reg(self, load_on_reg): 560 if not isinstance(load_on_reg, bool): 561 raise ValueError('Plugin must have load_on_reg=True or False') 562 self._load_on_reg = load_on_reg 563 564 def _get_load_on_reg(self): 565 return self._load_on_reg 566 567 def _get_icons(self): 568 return self._icons 569 570 def _set_icons(self, icons): 571 if not isinstance(icons, list): 572 raise ValueError('Plugin must have icons as a list') 573 self._icons = icons 574 575 def _get_icondir(self): 576 return self._icondir 577 578 def _set_icondir(self, icondir): 579 self._icondir = icondir 580 581 def _get_depends_on(self): 582 return self._depends_on 583 584 def _set_depends_on(self, depends): 585 if not isinstance(depends, list): 586 raise ValueError('Plugin must have depends_on as a list') 587 self._depends_on = depends 588 589 def _get_include_in_listing(self): 590 return self._include_in_listing 591 592 def _set_include_in_listing(self, include): 593 if not isinstance(include, bool): 594 raise ValueError('Plugin must have include_in_listing as a bool') 595 self._include_in_listing = include 596 597 id = property(_get_id, _set_id) 598 name = property(_get_name, _set_name) 599 name_accell = property(_get_name_accell, _set_name_accell) 600 description = property(_get_description, _set_description) 601 version = property(_get_version, _set_version) 602 gramps_target_version = property(_get_gramps_target_version, 603 _set_gramps_target_version) 604 status = property(_get_status, _set_status) 605 fname = property(_get_fname, _set_fname) 606 fpath = property(_get_fpath, _set_fpath) 607 ptype = property(_get_ptype, _set_ptype) 608 authors = property(_get_authors, _set_authors) 609 authors_email = property(_get_authors_email, _set_authors_email) 610 supported = property(_get_supported, _set_supported) 611 load_on_reg = property(_get_load_on_reg, _set_load_on_reg) 612 icons = property(_get_icons, _set_icons) 613 icondir = property(_get_icondir, _set_icondir) 614 depends_on = property(_get_depends_on, _set_depends_on) 615 include_in_listing = property(_get_include_in_listing, _set_include_in_listing) 616 617 def statustext(self): 618 return STATUSTEXT[self.status] 619 620 #type specific plugin attributes 621 622 #RELCALC attributes 623 def _set_relcalcclass(self, relcalcclass): 624 if not self._ptype == RELCALC: 625 raise ValueError('relcalcclass may only be set for RELCALC plugins') 626 self._relcalcclass = relcalcclass 627 628 def _get_relcalcclass(self): 629 return self._relcalcclass 630 631 def _set_lang_list(self, lang_list): 632 if not self._ptype == RELCALC: 633 raise ValueError('relcalcclass may only be set for RELCALC plugins') 634 self._lang_list = lang_list 635 636 def _get_lang_list(self): 637 return self._lang_list 638 639 relcalcclass = property(_get_relcalcclass, _set_relcalcclass) 640 lang_list = property(_get_lang_list, _set_lang_list) 641 642 #REPORT attributes 643 def _set_require_active(self, require_active): 644 if not self._ptype == REPORT: 645 raise ValueError('require_active may only be set for REPORT plugins') 646 if not isinstance(require_active, bool): 647 raise ValueError('Report must have require_active=True or False') 648 self._require_active = require_active 649 650 def _get_require_active(self): 651 return self._require_active 652 653 def _set_reportclass(self, reportclass): 654 if not self._ptype == REPORT: 655 raise ValueError('reportclass may only be set for REPORT plugins') 656 self._reportclass = reportclass 657 658 def _get_reportclass(self): 659 return self._reportclass 660 661 def _set_report_modes(self, report_modes): 662 if not self._ptype == REPORT: 663 raise ValueError('report_modes may only be set for REPORT plugins') 664 if not isinstance(report_modes, list): 665 raise ValueError('report_modes must be a list') 666 self._report_modes = [x for x in report_modes if x in REPORT_MODES] 667 if not self._report_modes: 668 raise ValueError('report_modes not a valid list of modes') 669 670 def _get_report_modes(self): 671 return self._report_modes 672 673 #REPORT or TOOL or QUICKREPORT or GENERAL attributes 674 def _set_category(self, category): 675 if self._ptype not in [REPORT, TOOL, QUICKREPORT, VIEW, GENERAL]: 676 raise ValueError('category may only be set for ' \ 677 'REPORT/TOOL/QUICKREPORT/VIEW/GENERAL plugins') 678 self._category = category 679 680 def _get_category(self): 681 return self._category 682 683 #REPORT OR TOOL attributes 684 def _set_optionclass(self, optionclass): 685 if not (self._ptype == REPORT or self.ptype == TOOL or self._ptype == DOCGEN): 686 raise ValueError('optionclass may only be set for REPORT/TOOL/DOCGEN plugins') 687 self._optionclass = optionclass 688 689 def _get_optionclass(self): 690 return self._optionclass 691 692 #TOOL attributes 693 def _set_toolclass(self, toolclass): 694 if not self._ptype == TOOL: 695 raise ValueError('toolclass may only be set for TOOL plugins') 696 self._toolclass = toolclass 697 698 def _get_toolclass(self): 699 return self._toolclass 700 701 def _set_tool_modes(self, tool_modes): 702 if not self._ptype == TOOL: 703 raise ValueError('tool_modes may only be set for TOOL plugins') 704 if not isinstance(tool_modes, list): 705 raise ValueError('tool_modes must be a list') 706 self._tool_modes = [x for x in tool_modes if x in TOOL_MODES] 707 if not self._tool_modes: 708 raise ValueError('tool_modes not a valid list of modes') 709 710 def _get_tool_modes(self): 711 return self._tool_modes 712 713 require_active = property(_get_require_active, _set_require_active) 714 reportclass = property(_get_reportclass, _set_reportclass) 715 report_modes = property(_get_report_modes, _set_report_modes) 716 category = property(_get_category, _set_category) 717 optionclass = property(_get_optionclass, _set_optionclass) 718 toolclass = property(_get_toolclass, _set_toolclass) 719 tool_modes = property(_get_tool_modes, _set_tool_modes) 720 721 #DOCGEN attributes 722 def _set_paper(self, paper): 723 if not self._ptype == DOCGEN: 724 raise ValueError('paper may only be set for DOCGEN plugins') 725 if not isinstance(paper, bool): 726 raise ValueError('Plugin must have paper=True or False') 727 self._paper = paper 728 729 def _get_paper(self): 730 return self._paper 731 732 def _set_style(self, style): 733 if not self._ptype == DOCGEN: 734 raise ValueError('style may only be set for DOCGEN plugins') 735 if not isinstance(style, bool): 736 raise ValueError('Plugin must have style=True or False') 737 self._style = style 738 739 def _get_style(self): 740 return self._style 741 742 def _set_extension(self, extension): 743 if not (self._ptype == DOCGEN or self._ptype == EXPORT 744 or self._ptype == IMPORT): 745 raise ValueError('extension may only be set for DOCGEN/EXPORT/'\ 746 'IMPORT plugins') 747 self._extension = extension 748 749 def _get_extension(self): 750 return self._extension 751 752 paper = property(_get_paper, _set_paper) 753 style = property(_get_style, _set_style) 754 extension = property(_get_extension, _set_extension) 755 756 #QUICKREPORT attributes 757 def _set_runfunc(self, runfunc): 758 if not self._ptype == QUICKREPORT: 759 raise ValueError('runfunc may only be set for QUICKREPORT plugins') 760 self._runfunc = runfunc 761 762 def _get_runfunc(self): 763 return self._runfunc 764 765 runfunc = property(_get_runfunc, _set_runfunc) 766 767 #MAPSERVICE attributes 768 def _set_mapservice(self, mapservice): 769 if not self._ptype == MAPSERVICE: 770 raise ValueError('mapservice may only be set for MAPSERVICE plugins') 771 self._mapservice = mapservice 772 773 def _get_mapservice(self): 774 return self._mapservice 775 776 mapservice = property(_get_mapservice, _set_mapservice) 777 778 #EXPORT attributes 779 def _set_export_function(self, export_function): 780 if not self._ptype == EXPORT: 781 raise ValueError('export_function may only be set for EXPORT plugins') 782 self._export_function = export_function 783 784 def _get_export_function(self): 785 return self._export_function 786 787 def _set_export_options(self, export_options): 788 if not self._ptype == EXPORT: 789 raise ValueError('export_options may only be set for EXPORT plugins') 790 self._export_options = export_options 791 792 def _get_export_options(self): 793 return self._export_options 794 795 def _set_export_options_title(self, export_options_title): 796 if not self._ptype == EXPORT: 797 raise ValueError('export_options_title may only be set for EXPORT plugins') 798 self._export_options_title = export_options_title 799 800 def _get_export_options_title(self): 801 return self._export_options_title 802 803 export_function = property(_get_export_function, _set_export_function) 804 export_options = property(_get_export_options, _set_export_options) 805 export_options_title = property(_get_export_options_title, 806 _set_export_options_title) 807 808 #IMPORT attributes 809 def _set_import_function(self, import_function): 810 if not self._ptype == IMPORT: 811 raise ValueError('import_function may only be set for IMPORT plugins') 812 self._import_function = import_function 813 814 def _get_import_function(self): 815 return self._import_function 816 817 import_function = property(_get_import_function, _set_import_function) 818 819 #GRAMPLET attributes 820 def _set_gramplet(self, gramplet): 821 if not self._ptype == GRAMPLET: 822 raise ValueError('gramplet may only be set for GRAMPLET plugins') 823 self._gramplet = gramplet 824 825 def _get_gramplet(self): 826 return self._gramplet 827 828 def _set_height(self, height): 829 if not self._ptype == GRAMPLET: 830 raise ValueError('height may only be set for GRAMPLET plugins') 831 if not isinstance(height, int): 832 raise ValueError('Plugin must have height an integer') 833 self._height = height 834 835 def _get_height(self): 836 return self._height 837 838 def _set_detached_height(self, detached_height): 839 if not self._ptype == GRAMPLET: 840 raise ValueError('detached_height may only be set for GRAMPLET plugins') 841 if not isinstance(detached_height, int): 842 raise ValueError('Plugin must have detached_height an integer') 843 self._detached_height = detached_height 844 845 def _get_detached_height(self): 846 return self._detached_height 847 848 def _set_detached_width(self, detached_width): 849 if not self._ptype == GRAMPLET: 850 raise ValueError('detached_width may only be set for GRAMPLET plugins') 851 if not isinstance(detached_width, int): 852 raise ValueError('Plugin must have detached_width an integer') 853 self._detached_width = detached_width 854 855 def _get_detached_width(self): 856 return self._detached_width 857 858 def _set_expand(self, expand): 859 if not self._ptype == GRAMPLET: 860 raise ValueError('expand may only be set for GRAMPLET plugins') 861 if not isinstance(expand, bool): 862 raise ValueError('Plugin must have expand as a bool') 863 self._expand = expand 864 865 def _get_expand(self): 866 return self._expand 867 868 def _set_gramplet_title(self, gramplet_title): 869 if not self._ptype == GRAMPLET: 870 raise ValueError('gramplet_title may only be set for GRAMPLET plugins') 871 if not isinstance(gramplet_title, str): 872 raise ValueError('gramplet_title is type %s, string or unicode required' % type(gramplet_title)) 873 self._gramplet_title = gramplet_title 874 875 def _get_gramplet_title(self): 876 return self._gramplet_title 877 878 def _set_help_url(self, help_url): 879 if not self._ptype == GRAMPLET: 880 raise ValueError('help_url may only be set for GRAMPLET plugins') 881 self._help_url = help_url 882 883 def _get_help_url(self): 884 return self._help_url 885 886 def _set_navtypes(self, navtypes): 887 if not self._ptype == GRAMPLET: 888 raise ValueError('navtypes may only be set for GRAMPLET plugins') 889 self._navtypes = navtypes 890 891 def _get_navtypes(self): 892 return self._navtypes 893 894 def _set_orientation(self, orientation): 895 if not self._ptype == GRAMPLET: 896 raise ValueError('orientation may only be set for GRAMPLET plugins') 897 self._orientation = orientation 898 899 def _get_orientation(self): 900 return self._orientation 901 902 gramplet = property(_get_gramplet, _set_gramplet) 903 height = property(_get_height, _set_height) 904 detached_height = property(_get_detached_height, _set_detached_height) 905 detached_width = property(_get_detached_width, _set_detached_width) 906 expand = property(_get_expand, _set_expand) 907 gramplet_title = property(_get_gramplet_title, _set_gramplet_title) 908 navtypes = property(_get_navtypes, _set_navtypes) 909 orientation = property(_get_orientation, _set_orientation) 910 help_url = property(_get_help_url, _set_help_url) 911 912 def _set_viewclass(self, viewclass): 913 if not self._ptype == VIEW: 914 raise ValueError('viewclass may only be set for VIEW plugins') 915 self._viewclass = viewclass 916 917 def _get_viewclass(self): 918 return self._viewclass 919 920 def _set_stock_icon(self, stock_icon): 921 if not self._ptype == VIEW: 922 raise ValueError('stock_icon may only be set for VIEW plugins') 923 self._stock_icon = stock_icon 924 925 def _get_stock_icon(self): 926 return self._stock_icon 927 928 viewclass = property(_get_viewclass, _set_viewclass) 929 stock_icon = property(_get_stock_icon, _set_stock_icon) 930 931 #SIDEBAR attributes 932 def _set_sidebarclass(self, sidebarclass): 933 if not self._ptype == SIDEBAR: 934 raise ValueError('sidebarclass may only be set for SIDEBAR plugins') 935 self._sidebarclass = sidebarclass 936 937 def _get_sidebarclass(self): 938 return self._sidebarclass 939 940 def _set_menu_label(self, menu_label): 941 if not self._ptype == SIDEBAR: 942 raise ValueError('menu_label may only be set for SIDEBAR plugins') 943 self._menu_label = menu_label 944 945 def _get_menu_label(self): 946 return self._menu_label 947 948 sidebarclass = property(_get_sidebarclass, _set_sidebarclass) 949 menu_label = property(_get_menu_label, _set_menu_label) 950 951 #VIEW and SIDEBAR attributes 952 def _set_order(self, order): 953 if not self._ptype in (VIEW, SIDEBAR): 954 raise ValueError('order may only be set for VIEW and SIDEBAR plugins') 955 self._order = order 956 957 def _get_order(self): 958 return self._order 959 960 order = property(_get_order, _set_order) 961 962 #DATABASE attributes 963 def _set_databaseclass(self, databaseclass): 964 if not self._ptype == DATABASE: 965 raise ValueError('databaseclass may only be set for DATABASE plugins') 966 self._databaseclass = databaseclass 967 968 def _get_databaseclass(self): 969 return self._databaseclass 970 971 def _set_reset_system(self, reset_system): 972 if not self._ptype == DATABASE: 973 raise ValueError('reset_system may only be set for DATABASE plugins') 974 self._reset_system = reset_system 975 976 def _get_reset_system(self): 977 return self._reset_system 978 979 databaseclass = property(_get_databaseclass, _set_databaseclass) 980 reset_system = property(_get_reset_system, _set_reset_system) 981 982 #GENERAL attr 983 def _set_data(self, data): 984 if not self._ptype in (GENERAL,): 985 raise ValueError('data may only be set for GENERAL plugins') 986 self._data = data 987 988 def _get_data(self): 989 return self._data 990 991 def _set_process(self, process): 992 if not self._ptype in (GENERAL,): 993 raise ValueError('process may only be set for GENERAL plugins') 994 self._process = process 995 996 def _get_process(self): 997 return self._process 998 999 data = property(_get_data, _set_data) 1000 process = property(_get_process, _set_process) 1001 1002 #RULE attr 1003 def _set_ruleclass(self, data): 1004 if self._ptype != RULE: 1005 raise ValueError('ruleclass may only be set for RULE plugins') 1006 self._ruleclass = data 1007 1008 def _get_ruleclass(self): 1009 return self._ruleclass 1010 1011 def _set_namespace(self, data): 1012 if self._ptype != RULE: 1013 raise ValueError('namespace may only be set for RULE plugins') 1014 self._namespace = data 1015 1016 def _get_namespace(self): 1017 return self._namespace 1018 1019 ruleclass = property(_get_ruleclass, _set_ruleclass) 1020 namespace = property(_get_namespace, _set_namespace) 1021 1022def newplugin(): 1023 """ 1024 Function to create a new plugindata object, add it to list of 1025 registered plugins 1026 1027 :returns: a newly created PluginData which is already part of the register 1028 """ 1029 gpr = PluginRegister.get_instance() 1030 pgd = PluginData() 1031 gpr.add_plugindata(pgd) 1032 return pgd 1033 1034def register(ptype, **kwargs): 1035 """ 1036 Convenience function to register a new plugin using a dictionary as input. 1037 The register functions will call newplugin() function, and use the 1038 dictionary kwargs to assign data to the PluginData newplugin() created, 1039 as in: plugindata.key = data 1040 1041 :param ptype: the plugin type, one of REPORT, TOOL, ... 1042 :param kwargs: dictionary with keys attributes of the plugin, and data 1043 the value 1044 :returns: a newly created PluginData which is already part of the register 1045 and which has kwargs assigned as attributes 1046 """ 1047 plg = newplugin() 1048 plg.ptype = ptype 1049 for prop in kwargs: 1050 #check it is a valid attribute with getattr 1051 getattr(plg, prop) 1052 #set the value 1053 setattr(plg, prop, kwargs[prop]) 1054 return plg 1055 1056def make_environment(**kwargs): 1057 env = { 1058 'newplugin': newplugin, 1059 'register': register, 1060 'STABLE': STABLE, 1061 'UNSTABLE': UNSTABLE, 1062 'REPORT': REPORT, 1063 'QUICKREPORT': QUICKREPORT, 1064 'TOOL': TOOL, 1065 'IMPORT': IMPORT, 1066 'EXPORT': EXPORT, 1067 'DOCGEN': DOCGEN, 1068 'GENERAL': GENERAL, 1069 'RULE': RULE, 1070 'MAPSERVICE': MAPSERVICE, 1071 'VIEW': VIEW, 1072 'RELCALC': RELCALC, 1073 'GRAMPLET': GRAMPLET, 1074 'SIDEBAR': SIDEBAR, 1075 'CATEGORY_TEXT': CATEGORY_TEXT, 1076 'CATEGORY_DRAW': CATEGORY_DRAW, 1077 'CATEGORY_CODE': CATEGORY_CODE, 1078 'CATEGORY_WEB': CATEGORY_WEB, 1079 'CATEGORY_BOOK': CATEGORY_BOOK, 1080 'CATEGORY_GRAPHVIZ': CATEGORY_GRAPHVIZ, 1081 'CATEGORY_TREE': CATEGORY_TREE, 1082 'TOOL_DEBUG': TOOL_DEBUG, 1083 'TOOL_ANAL': TOOL_ANAL, 1084 'TOOL_DBPROC': TOOL_DBPROC, 1085 'TOOL_DBFIX': TOOL_DBFIX, 1086 'TOOL_REVCTL': TOOL_REVCTL, 1087 'TOOL_UTILS': TOOL_UTILS, 1088 'CATEGORY_QR_MISC': CATEGORY_QR_MISC, 1089 'CATEGORY_QR_PERSON': CATEGORY_QR_PERSON, 1090 'CATEGORY_QR_FAMILY': CATEGORY_QR_FAMILY, 1091 'CATEGORY_QR_EVENT': CATEGORY_QR_EVENT, 1092 'CATEGORY_QR_SOURCE': CATEGORY_QR_SOURCE, 1093 'CATEGORY_QR_CITATION': CATEGORY_QR_CITATION, 1094 'CATEGORY_QR_SOURCE_OR_CITATION': CATEGORY_QR_SOURCE_OR_CITATION, 1095 'CATEGORY_QR_PLACE': CATEGORY_QR_PLACE, 1096 'CATEGORY_QR_MEDIA': CATEGORY_QR_MEDIA, 1097 'CATEGORY_QR_REPOSITORY': CATEGORY_QR_REPOSITORY, 1098 'CATEGORY_QR_NOTE': CATEGORY_QR_NOTE, 1099 'CATEGORY_QR_DATE': CATEGORY_QR_DATE, 1100 'REPORT_MODE_GUI': REPORT_MODE_GUI, 1101 'REPORT_MODE_BKI': REPORT_MODE_BKI, 1102 'REPORT_MODE_CLI': REPORT_MODE_CLI, 1103 'TOOL_MODE_GUI': TOOL_MODE_GUI, 1104 'TOOL_MODE_CLI': TOOL_MODE_CLI, 1105 'DATABASE': DATABASE, 1106 'GRAMPSVERSION': GRAMPSVERSION, 1107 'START': START, 1108 'END': END, 1109 'IMAGE_DIR': IMAGE_DIR, 1110 } 1111 env.update(kwargs) 1112 return env 1113 1114#------------------------------------------------------------------------- 1115# 1116# PluginRegister 1117# 1118#------------------------------------------------------------------------- 1119class PluginRegister: 1120 """ 1121 PluginRegister is a Singleton which holds plugin data 1122 1123 .. attribute : stable_only 1124 Bool, include stable plugins only or not. Default True 1125 """ 1126 __instance = None 1127 1128 def get_instance(): 1129 """ Use this function to get the instance of the PluginRegister """ 1130 if PluginRegister.__instance is None: 1131 PluginRegister.__instance = 1 # Set to 1 for __init__() 1132 PluginRegister.__instance = PluginRegister() 1133 return PluginRegister.__instance 1134 get_instance = staticmethod(get_instance) 1135 1136 def __init__(self): 1137 """ This function should only be run once by get_instance() """ 1138 if PluginRegister.__instance != 1: 1139 raise Exception("This class is a singleton. " 1140 "Use the get_instance() method") 1141 self.stable_only = True 1142 if __debug__: 1143 self.stable_only = False 1144 self.__plugindata = [] 1145 self.__id_to_pdata = {} 1146 1147 def add_plugindata(self, plugindata): 1148 """ This is used to add an entry to the registration list. The way it 1149 is used, this entry is not yet filled in, so we cannot use the id to 1150 add to the __id_to_pdata dict at this time. """ 1151 self.__plugindata.append(plugindata) 1152 1153 1154 def scan_dir(self, dir, filenames, uistate=None): 1155 """ 1156 The dir name will be scanned for plugin registration code, which will 1157 be loaded in :class:`PluginData` objects if they satisfy some checks. 1158 1159 :returns: A list with :class:`PluginData` objects 1160 """ 1161 # if the directory does not exist, do nothing 1162 if not (os.path.isdir(dir) or os.path.islink(dir)): 1163 return [] 1164 1165 ext = r".gpr.py" 1166 extlen = -len(ext) 1167 pymod = re.compile(r"^(.*)\.py$") 1168 1169 for filename in filenames: 1170 if not filename[extlen:] == ext: 1171 continue 1172 lenpd = len(self.__plugindata) 1173 full_filename = os.path.join(dir, filename) 1174 try: 1175 with open(full_filename, "r", encoding='utf-8') as fd: 1176 stream = fd.read() 1177 except Exception as msg: 1178 print(_('ERROR: Failed reading plugin registration %(filename)s') % \ 1179 {'filename' : filename}) 1180 print(msg) 1181 continue 1182 if os.path.exists(os.path.join(os.path.dirname(full_filename), 1183 'locale')): 1184 try: 1185 local_gettext = glocale.get_addon_translator(full_filename).gettext 1186 except ValueError: 1187 print(_('WARNING: Plugin %(plugin_name)s has no translation' 1188 ' for any of your configured languages, using US' 1189 ' English instead') % 1190 {'plugin_name' : filename.split('.')[0] }) 1191 local_gettext = glocale.translation.gettext 1192 else: 1193 local_gettext = glocale.translation.gettext 1194 try: 1195 exec (compile(stream, filename, 'exec'), 1196 make_environment(_=local_gettext), {'uistate': uistate}) 1197 for pdata in self.__plugindata[lenpd:]: 1198 # should not be duplicate IDs in different plugins 1199 assert pdata.id not in self.__id_to_pdata 1200 # if pdata.id in self.__id_to_pdata: 1201 # print("Error: %s is duplicated!" % pdata.id) 1202 self.__id_to_pdata[pdata.id] = pdata 1203 except ValueError as msg: 1204 print(_('ERROR: Failed reading plugin registration %(filename)s') % \ 1205 {'filename' : filename}) 1206 print(msg) 1207 self.__plugindata = self.__plugindata[:lenpd] 1208 except: 1209 print(_('ERROR: Failed reading plugin registration %(filename)s') % \ 1210 {'filename' : filename}) 1211 print("".join(traceback.format_exception(*sys.exc_info()))) 1212 self.__plugindata = self.__plugindata[:lenpd] 1213 #check if: 1214 # 1. plugin exists, if not remove, otherwise set module name 1215 # 2. plugin not stable, if stable_only=True, remove 1216 # 3. TOOL_DEBUG only if __debug__ True 1217 rmlist = [] 1218 ind = lenpd-1 1219 for plugin in self.__plugindata[lenpd:]: 1220 #LOG.warning("\nPlugin scanned %s at registration", plugin.id) 1221 ind += 1 1222 plugin.directory = dir 1223 if not valid_plugin_version(plugin.gramps_target_version): 1224 print(_('ERROR: Plugin file %(filename)s has a version of ' 1225 '"%(gramps_target_version)s" which is invalid for Gramps ' 1226 '"%(gramps_version)s".' % 1227 {'filename': os.path.join(dir, plugin.fname), 1228 'gramps_version': GRAMPSVERSION, 1229 'gramps_target_version': plugin.gramps_target_version,} 1230 )) 1231 rmlist.append(ind) 1232 continue 1233 if not plugin.status == STABLE and self.stable_only: 1234 rmlist.append(ind) 1235 continue 1236 if plugin.ptype == TOOL and plugin.category == TOOL_DEBUG \ 1237 and not __debug__: 1238 rmlist.append(ind) 1239 continue 1240 if plugin.fname is None: 1241 continue 1242 match = pymod.match(plugin.fname) 1243 if not match: 1244 rmlist.append(ind) 1245 print(_('ERROR: Wrong python file %(filename)s in register file ' 1246 '%(regfile)s') % { 1247 'filename': os.path.join(dir, plugin.fname), 1248 'regfile': os.path.join(dir, filename) 1249 }) 1250 continue 1251 if not os.path.isfile(os.path.join(dir, plugin.fname)): 1252 rmlist.append(ind) 1253 print(_('ERROR: Python file %(filename)s in register file ' 1254 '%(regfile)s does not exist') % { 1255 'filename': os.path.join(dir, plugin.fname), 1256 'regfile': os.path.join(dir, filename) 1257 }) 1258 continue 1259 module = match.groups()[0] 1260 plugin.mod_name = module 1261 plugin.fpath = dir 1262 #LOG.warning("\nPlugin added %s at registration", plugin.id) 1263 rmlist.reverse() 1264 for ind in rmlist: 1265 del self.__id_to_pdata[self.__plugindata[ind].id] 1266 del self.__plugindata[ind] 1267 1268 def get_plugin(self, id): 1269 """ 1270 Return the :class:`PluginData` for the plugin with id 1271 """ 1272 assert(len(self.__id_to_pdata) == len(self.__plugindata)) 1273 # if len(self.__id_to_pdata) != len(self.__plugindata): 1274 # print(len(self.__id_to_pdata), len(self.__plugindata)) 1275 return self.__id_to_pdata.get(id, None) 1276 1277 def type_plugins(self, ptype): 1278 """ 1279 Return a list of :class:`PluginData` that are of type ptype 1280 """ 1281 return [x for x in self.__plugindata if x.ptype == ptype] 1282 1283 def report_plugins(self, gui=True): 1284 """ 1285 Return a list of gui or cli :class:`PluginData` that are of type REPORT 1286 1287 :param gui: bool, if True then gui plugin, otherwise cli plugin 1288 """ 1289 if gui: 1290 return [x for x in self.type_plugins(REPORT) if REPORT_MODE_GUI 1291 in x.report_modes] 1292 else: 1293 return [x for x in self.type_plugins(REPORT) if REPORT_MODE_CLI 1294 in x.report_modes] 1295 1296 def tool_plugins(self, gui=True): 1297 """ 1298 Return a list of :class:`PluginData` that are of type TOOL 1299 """ 1300 if gui: 1301 return [x for x in self.type_plugins(TOOL) if TOOL_MODE_GUI 1302 in x.tool_modes] 1303 else: 1304 return [x for x in self.type_plugins(TOOL) if TOOL_MODE_CLI 1305 in x.tool_modes] 1306 1307 1308 def bookitem_plugins(self): 1309 """ 1310 Return a list of REPORT :class:`PluginData` that are can be used as 1311 bookitem 1312 """ 1313 return [x for x in self.type_plugins(REPORT) if REPORT_MODE_BKI 1314 in x.report_modes] 1315 1316 def quickreport_plugins(self): 1317 """ 1318 Return a list of :class:`PluginData` that are of type QUICKREPORT 1319 """ 1320 return self.type_plugins(QUICKREPORT) 1321 1322 def import_plugins(self): 1323 """ 1324 Return a list of :class:`PluginData` that are of type IMPORT 1325 """ 1326 return self.type_plugins(IMPORT) 1327 1328 def export_plugins(self): 1329 """ 1330 Return a list of :class:`PluginData` that are of type EXPORT 1331 """ 1332 return self.type_plugins(EXPORT) 1333 1334 def docgen_plugins(self): 1335 """ 1336 Return a list of :class:`PluginData` that are of type DOCGEN 1337 """ 1338 return self.type_plugins(DOCGEN) 1339 1340 def general_plugins(self, category=None): 1341 """ 1342 Return a list of :class:`PluginData` that are of type GENERAL 1343 """ 1344 plugins = self.type_plugins(GENERAL) 1345 if category: 1346 return [plugin for plugin in plugins 1347 if plugin.category == category] 1348 return plugins 1349 1350 def mapservice_plugins(self): 1351 """ 1352 Return a list of :class:`PluginData` that are of type MAPSERVICE 1353 """ 1354 return self.type_plugins(MAPSERVICE) 1355 1356 def view_plugins(self): 1357 """ 1358 Return a list of :class:`PluginData` that are of type VIEW 1359 """ 1360 return self.type_plugins(VIEW) 1361 1362 def relcalc_plugins(self): 1363 """ 1364 Return a list of :class:`PluginData` that are of type RELCALC 1365 """ 1366 return self.type_plugins(RELCALC) 1367 1368 def gramplet_plugins(self): 1369 """ 1370 Return a list of :class:`PluginData` that are of type GRAMPLET 1371 """ 1372 return self.type_plugins(GRAMPLET) 1373 1374 def sidebar_plugins(self): 1375 """ 1376 Return a list of :class:`PluginData` that are of type SIDEBAR 1377 """ 1378 return self.type_plugins(SIDEBAR) 1379 1380 def database_plugins(self): 1381 """ 1382 Return a list of :class:`PluginData` that are of type DATABASE 1383 """ 1384 return self.type_plugins(DATABASE) 1385 1386 def rule_plugins(self): 1387 """ 1388 Return a list of :class:`PluginData` that are of type RULE 1389 """ 1390 return self.type_plugins(RULE) 1391 1392 def filter_load_on_reg(self): 1393 """ 1394 Return a list of :class:`PluginData` that have load_on_reg == True 1395 """ 1396 return [x for x in self.__plugindata if x.load_on_reg == True] 1397