gramps/src/PluginUtils/_PluginMgr.py

566 lines
20 KiB
Python
Raw Normal View History

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2005 Donald N. Allingham
#
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id$
"""
The core of the GRAMPS plugin system. This module provides tasks to load
plugins from specfied directories, build menus for the different categories,
and provide dialog to select and execute plugins.
Plugins are divided into several categories. This are: reports, tools,
importers, exporters, and document generators.
"""
#-------------------------------------------------------------------------
#
# Standard Python modules
#
#-------------------------------------------------------------------------
import os
import sys
import re
from gettext import gettext as _
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
from ReportBase import MODE_GUI, MODE_CLI, MODE_BKI, book_categories
#-------------------------------------------------------------------------
#
# Global lists
#
#-------------------------------------------------------------------------
report_list = []
quick_report_list = []
tool_list = []
import_list = []
export_list = []
attempt_list = []
loaddir_list = []
textdoc_list = []
bookdoc_list = []
drawdoc_list = []
failmsg_list = []
bkitems_list = []
cl_list = []
2005-12-06 12:08:09 +05:30
cli_tool_list = []
2006-04-21 22:38:34 +05:30
success_list = []
2006-04-22 03:54:54 +05:30
mod2text = {}
#-------------------------------------------------------------------------
#
# Default relationship calculator
#
#-------------------------------------------------------------------------
import Relationship
_relcalc_class = Relationship.RelationshipCalculator
#-------------------------------------------------------------------------
#
2005-12-06 12:08:09 +05:30
# Constants
#
#-------------------------------------------------------------------------
_unavailable = _("No description was provided"),
#-------------------------------------------------------------------------
#
# load_plugins
#
#-------------------------------------------------------------------------
def load_plugins(direct):
"""Searches the specified directory, and attempts to load any python
modules that it finds, adding name to the attempt_lists list. If the module
2006-04-21 22:38:34 +05:30
successfully loads, it is added to the success_list list. Each plugin is
responsible for registering itself in the correct manner. No attempt
is done in this routine to register the tasks. Returns True on error. """
# if the directory does not exist, do nothing
if not os.path.isdir(direct):
return False # return value is True for error
# if the path has not already been loaded, save it in the loaddir_list
# list for use on reloading
if direct not in loaddir_list:
loaddir_list.append(direct)
# add the directory to the python search path
sys.path.append(direct)
pymod = re.compile(r"^(.*)\.py$")
# loop through each file in the directory, looking for files that
# have a .py extention, and attempt to load the file. If it succeeds,
2006-04-21 22:38:34 +05:30
# add it to the success_list list. If it fails, add it to the _failure
# list
for filename in os.listdir(direct):
name = os.path.split(filename)
match = pymod.match(name[1])
if not match:
continue
attempt_list.append(filename)
plugin = match.groups()[0]
try:
_module = __import__(plugin)
success_list.append((filename, _module))
except:
failmsg_list.append((filename, sys.exc_info()))
return len(failmsg_list) != 0 # return True if there are errors
#-------------------------------------------------------------------------
#
# reload_plugins
#
#-------------------------------------------------------------------------
def reload_plugins():
""" Reload previously loaded plugins """
global failmsg_list
pymod = re.compile(r"^(.*)\.py$")
oldfailmsg = failmsg_list[:]
failmsg_list = []
# attempt to reload all plugins that have succeeded in the past
for plugin in success_list:
filename = plugin[0]
filename = filename.replace('pyc','py')
filename = filename.replace('pyo','py')
try:
reload(plugin[1])
except:
failmsg_list.append((filename, sys.exc_info()))
# Remove previously good plugins that are now bad
# from the registered lists
purge_failed()
# attempt to load the plugins that have failed in the past
for (filename, message) in oldfailmsg:
name = os.path.split(filename)
match = pymod.match(name[1])
if not match:
continue
attempt_list.append(filename)
plugin = match.groups()[0]
try:
# For some strange reason second importing of a failed plugin
# results in success. Then reload reveals the actual error.
# Looks like a bug in Python.
_module = __import__(plugin)
reload(_module)
success_list.append((filename, _module))
except:
failmsg_list.append((filename, sys.exc_info()))
# attempt to load any new files found
for directory in loaddir_list:
for filename in os.listdir(directory):
name = os.path.split(filename)
match = pymod.match(name[1])
if not match:
continue
if filename in attempt_list:
continue
attempt_list.append(filename)
plugin = match.groups()[0]
try:
_module = __import__(plugin)
if _module not in [plugin[1]
for plugin in success_list]:
success_list.append((filename, _module))
except:
failmsg_list.append((filename, sys.exc_info()))
#-------------------------------------------------------------------------
#
# Plugin registering
#
#-------------------------------------------------------------------------
def register_export(exportData, title, description='', config=None,
filename=''):
"""
Register an export filter, taking the task, file filter,
and the list of patterns for the filename matching.
"""
if description and filename:
2005-12-06 12:08:09 +05:30
del_index = -1
for i in range(0, len(export_list)):
2005-12-06 12:08:09 +05:30
if export_list[i][1] == title:
del_index = i
if del_index != -1:
del export_list[del_index]
export_list.append((exportData, title, description, config, filename))
2006-04-22 03:54:54 +05:30
mod2text[exportData.__module__] = description
def register_import(task, ffilter, mime=None, native_format=0, format_name=""):
"""Register an import filter, taking the task and file filter"""
if mime:
2005-12-06 12:08:09 +05:30
del_index = -1
for i in range(0, len(import_list)):
2005-12-06 12:08:09 +05:30
if import_list[i][2] == mime:
del_index = i
if del_index != -1:
del import_list[del_index]
2005-12-06 12:08:09 +05:30
import_list.append((task, ffilter, mime, native_format, format_name))
2006-04-22 03:54:54 +05:30
mod2text[task.__module__] = format_name
2005-12-06 12:08:09 +05:30
#-------------------------------------------------------------------------
#
# Tool registration
#
#-------------------------------------------------------------------------
def register_tool(
name,
2005-12-06 12:08:09 +05:30
category,
tool_class,
options_class,
modes,
translated_name,
status=_("Unknown"),
2005-12-06 12:08:09 +05:30
description=_unavailable,
author_name=_("Unknown"),
2005-12-06 12:08:09 +05:30
author_email=_("Unknown"),
2006-06-17 02:56:44 +05:30
unsupported=False,
require_active=True,
):
2005-12-06 12:08:09 +05:30
"""
Register a tool with the plugin system.
This function should be used to register tool in GUI and/or
command-line mode. The low-level functions (starting with '_')
should not be used on their own. Instead, this functino will call
them as needed.
"""
import _Tool
2006-04-22 03:54:54 +05:30
(junk, gui_task) = divmod(modes, 2**_Tool.MODE_GUI)
2005-12-06 12:08:09 +05:30
if gui_task:
_register_gui_tool(tool_class, options_class, translated_name,
name, category, description,
status, author_name, author_email, unsupported,
2006-06-17 02:56:44 +05:30
require_active)
2005-12-06 12:08:09 +05:30
(junk, cli_task) = divmod(modes-gui_task, 2**_Tool.MODE_CLI)
2005-12-06 12:08:09 +05:30
if cli_task:
_register_cli_tool(name, category, tool_class, options_class,
translated_name, unsupported)
2005-12-06 12:08:09 +05:30
def _register_gui_tool(tool_class, options_class, translated_name,
2005-12-06 12:08:09 +05:30
name,category,
description=_unavailable,
status=_("Unknown"),
author_name=_("Unknown"),
author_email=_("Unknown"),
2006-06-17 02:56:44 +05:30
unsupported=False,
require_active=True):
del_index = -1
for i in range(0, len(tool_list)):
val = tool_list[i]
2005-12-06 12:08:09 +05:30
if val[4] == name:
del_index = i
if del_index != -1:
del tool_list[del_index]
2006-04-22 03:54:54 +05:30
mod2text[tool_class.__module__] = description
tool_list.append((tool_class, options_class, translated_name,
category, name, description, status,
author_name, author_email, unsupported, require_active))
2005-12-06 12:08:09 +05:30
def _register_cli_tool(name, category, tool_class, options_class,
translated_name, unsupported=False):
2005-12-06 12:08:09 +05:30
del_index = -1
for i in range(0, len(cli_tool_list)):
2005-12-06 12:08:09 +05:30
val = cli_tool_list[i]
if val[0] == name:
del_index = i
if del_index != -1:
del cli_tool_list[del_index]
cli_tool_list.append((name, category, tool_class, options_class,
translated_name, unsupported, None))
#-------------------------------------------------------------------------
#
# Report registration
#
#-------------------------------------------------------------------------
def register_report(
name,
category,
report_class,
options_class,
modes,
translated_name,
status=_("Unknown"),
description=_unavailable,
author_name=_("Unknown"),
2005-12-06 12:08:09 +05:30
author_email=_("Unknown"),
2006-06-17 02:56:44 +05:30
unsupported=False,
require_active=True,
):
"""
Registers report for all possible flavors.
2005-12-06 12:08:09 +05:30
This function should be used to register report as a stand-alone,
book item, or command-line flavor in any combination of those.
The low-level functions (starting with '_') should not be used
on their own. Instead, this function will call them as needed.
"""
(junk, standalone_task) = divmod(modes, 2**MODE_GUI)
if standalone_task:
_register_standalone(report_class, options_class, translated_name,
name, category, description,
status, author_name, author_email, unsupported,
2006-06-17 02:56:44 +05:30
require_active)
(junk, book_item_task) = divmod(modes-standalone_task, 2**MODE_BKI)
if book_item_task:
2006-05-31 08:11:46 +05:30
book_item_category = book_categories[category]
register_book_item(translated_name, book_item_category,
report_class, options_class, name, unsupported,
2006-06-17 02:56:44 +05:30
require_active)
(junk, command_line_task) = divmod(modes-standalone_task-book_item_task,
2006-05-31 08:11:46 +05:30
2**MODE_CLI)
if command_line_task:
_register_cl_report(name, category, report_class, options_class,
translated_name, unsupported, require_active)
def _register_standalone(report_class, options_class, translated_name, name,
category, description=_unavailable,
status=_("Unknown"), author_name=_("Unknown"),
author_email=_("Unknown"), unsupported=False,
require_active=True):
"""Register a report with the plugin system."""
del_index = -1
for i in range(0, len(report_list)):
val = report_list[i]
2005-12-06 12:08:09 +05:30
if val[4] == name:
del_index = i
if del_index != -1:
del report_list[del_index]
report_list.append((report_class, options_class, translated_name,
category, name, description, status, author_name,
author_email, unsupported, require_active))
2006-04-22 03:54:54 +05:30
mod2text[report_class.__module__] = description
def register_book_item(translated_name, category, report_class,
2006-06-17 02:56:44 +05:30
option_class, name, unsupported, require_active):
"""Register a book item."""
2005-12-06 12:08:09 +05:30
del_index = -1
for i in range(0, len(bkitems_list)):
2005-12-06 12:08:09 +05:30
val = bkitems_list[i]
if val[4] == name:
del_index = i
if del_index != -1:
del bkitems_list[del_index]
bkitems_list.append((translated_name, category, report_class,
2006-06-17 02:56:44 +05:30
option_class, name, unsupported, require_active))
def _register_cl_report(name, category, report_class, options_class,
translated_name, unsupported, require_active):
2005-12-06 12:08:09 +05:30
del_index = -1
for i in range(0, len(cl_list)):
2005-12-06 12:08:09 +05:30
val = cl_list[i]
if val[0] == name:
del_index = i
if del_index != -1:
del cl_list[del_index]
cl_list.append((name, category, report_class, options_class,
translated_name, unsupported, require_active))
#-------------------------------------------------------------------------
#
# Text document generator registration
#
#-------------------------------------------------------------------------
def register_text_doc(name, classref, table, paper, style, ext,
print_report_label=None, clname=''):
"""Register a text document generator."""
2005-12-06 12:08:09 +05:30
del_index = -1
for i in range(0, len(textdoc_list)):
2005-12-06 12:08:09 +05:30
val = textdoc_list[i]
if val[0] == name:
del_index = i
if del_index != -1:
del textdoc_list[del_index]
if not clname:
clname = ext[1:]
2005-12-06 12:08:09 +05:30
textdoc_list.append(
(name, classref, table, paper, style,
ext, print_report_label, clname))
2006-04-22 03:54:54 +05:30
mod2text[classref.__module__] = name
#-------------------------------------------------------------------------
#
# Book document generator registration
#
#-------------------------------------------------------------------------
def register_book_doc(name, classref, table, paper, style, ext,
print_report_label=None, clname=''):
"""Register a text document generator"""
2005-12-06 12:08:09 +05:30
del_index = -1
for i in range(0, len(bookdoc_list)):
2005-12-06 12:08:09 +05:30
val = bookdoc_list[i]
if val[0] == name:
del_index = i
if del_index != -1:
del bookdoc_list[del_index]
if not clname:
clname = ext[1:]
bookdoc_list.append((name, classref, table, paper, style, ext,
print_report_label, clname))
#-------------------------------------------------------------------------
#
# Drawing document generator registration
#
#-------------------------------------------------------------------------
def register_draw_doc(name, classref, paper, style, ext,
print_report_label=None, clname=''):
"""Register a drawing document generator"""
2005-12-06 12:08:09 +05:30
del_index = -1
for i in range(0, len(drawdoc_list)):
2005-12-06 12:08:09 +05:30
val = drawdoc_list[i]
if val[0] == name:
del_index = i
if del_index != -1:
del drawdoc_list[del_index]
if not clname:
clname = ext[1:]
drawdoc_list.append((name, classref, paper, style, ext,
print_report_label, clname))
2006-04-22 03:54:54 +05:30
mod2text[classref.__module__] = name
#-------------------------------------------------------------------------
#
# Quick Report registration
#
#-------------------------------------------------------------------------
def register_quick_report(
name,
category,
run_func,
translated_name,
status=_("Unknown"),
description=_unavailable,
author_name=_("Unknown"),
author_email=_("Unknown"),
unsupported=False,
):
"""
Registers quick report for all possible objects.
This function should be used to register a quick report
so it appears in the quick report context menu of the object it is
attached to.
The low-level functions (starting with '_') should not be used
on their own. Instead, this function will call them as needed.
"""
del_index = -1
for i in range(0, len(quick_report_list)):
val = quick_report_list[i]
if val[3] == name:
del_index = i
if del_index != -1:
del quick_report_list[del_index]
quick_report_list.append((run_func, translated_name,
category, name, description, status,
author_name, author_email, unsupported))
mod2text[run_func.__module__] = description
2005-12-06 12:08:09 +05:30
#-------------------------------------------------------------------------
#
# Remove plugins whose reloading failed from the already-registered lists
#
#-------------------------------------------------------------------------
def purge_failed():
global report_list, quick_report_list, tool_list, import_list, export_list,\
textdoc_list, bookdoc_list, drawdoc_list, bkitems_list, cl_list, \
cli_tool_list, failmsg_list
2005-12-06 12:08:09 +05:30
failed_module_names = [
os.path.splitext(os.path.basename(filename))[0]
for filename, junk in failmsg_list
2005-12-06 12:08:09 +05:30
]
#although these are global variables, we may not change the pointer of
# the list, as __init__.py already contains these, see comment there
export_list[:] = [ item for item in export_list
if item[0].__module__ not in failed_module_names ][:]
import_list[:] = [ item for item in import_list
if item[0].__module__ not in failed_module_names ][:]
tool_list[:] = [ item for item in tool_list
if item[0].__module__ not in failed_module_names ][:]
cli_tool_list[:] = [ item for item in cli_tool_list
if item[2].__module__ not in failed_module_names ][:]
report_list[:] = [ item for item in report_list
if item[0].__module__ not in failed_module_names ][:]
quick_report_list[:] = [ item for item in quick_report_list
if item[0].__module__ not in failed_module_names ][:]
bkitems_list[:] = [ item for item in bkitems_list
if item[2].__module__ not in failed_module_names ][:]
cl_list[:] = [ item for item in cl_list
if item[2].__module__ not in failed_module_names ][:]
textdoc_list[:] = [ item for item in textdoc_list
if item[1].__module__ not in failed_module_names ][:]
bookdoc_list[:] = [ item for item in bookdoc_list
if item[1].__module__ not in failed_module_names ][:]
drawdoc_list[:] = [ item for item in drawdoc_list
if item[1].__module__ not in failed_module_names ][:]
2005-12-06 12:08:09 +05:30
#-------------------------------------------------------------------------
#
# Relationship calculator registration
#
#-------------------------------------------------------------------------
def register_relcalc(relclass, languages):
"""Register a relationshp calculator"""
global _relcalc_class
try:
if os.environ["LANG"] in languages:
_relcalc_class = relclass
except:
pass
def relationship_class():
global _relcalc_class
return _relcalc_class()