changeset 51:54d5932d5a03

merge moin--main--1.3--patch-930: fix error handling in plugins, fix broken chart action Patches applied: * arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-930 fix error handling in plugins, fix broken chart action * nirs@freeshell.org--2005/moin--fix--1.3--patch-50 merge from main * nirs@freeshell.org--2005/moin--fix--1.3--patch-51 fix error handling in plugins * nirs@freeshell.org--2005/moin--fix--1.3--patch-52 merge from main * nirs@freeshell.org--2005/moin--fix--1.3--patch-53 update changes with chart action fix imported from: moin--main--1.5--patch-52
author Nir Soffer <nirs@freeshell.org>
date Thu, 29 Sep 2005 00:36:27 +0000
parents 69dfbae549ac
children 80b3d4e891ff
files ChangeLog MoinMoin/Page.py MoinMoin/_tests/test_pysupport.py MoinMoin/formatter/text_python.py MoinMoin/i18n/__init__.py MoinMoin/macro/StatsChart.py MoinMoin/parser/wiki.py MoinMoin/request.py MoinMoin/util/pysupport.py MoinMoin/wikiaction.py MoinMoin/wikimacro.py MoinMoin/wikirpc.py MoinMoin/wikiutil.py docs/CHANGES
diffstat 14 files changed, 246 insertions(+), 140 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Thu Sep 29 00:09:43 2005 +0000
+++ b/ChangeLog	Thu Sep 29 00:36:27 2005 +0000
@@ -36,6 +36,43 @@
      MoinMoin/wikiaction.py
 
 
+2005-09-04 00:24:17 GMT	Nir Soffer <nirs@freeshell.org>	patch-930
+
+    Summary:
+      fix error handling in plugins, fix broken chart action
+    Revision:
+      moin--main--1.3--patch-930
+
+    Patches applied:
+    
+     * nirs@freeshell.org--2005/moin--fix--1.3--patch-50
+       merge from main
+    
+     * nirs@freeshell.org--2005/moin--fix--1.3--patch-51
+       fix error handling in plugins
+    
+     * nirs@freeshell.org--2005/moin--fix--1.3--patch-52
+       merge from main
+    
+     * nirs@freeshell.org--2005/moin--fix--1.3--patch-53
+       update changes with chart action fix
+    
+
+    modified files:
+     ChangeLog MoinMoin/Page.py MoinMoin/_tests/test_pysupport.py
+     MoinMoin/formatter/text_python.py MoinMoin/i18n/__init__.py
+     MoinMoin/macro/StatsChart.py MoinMoin/parser/wiki.py
+     MoinMoin/request.py MoinMoin/util/pysupport.py
+     MoinMoin/wikiaction.py MoinMoin/wikimacro.py
+     MoinMoin/wikirpc.py MoinMoin/wikiutil.py docs/CHANGES
+
+    new patches:
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-50
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-51
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-52
+     nirs@freeshell.org--2005/moin--fix--1.3--patch-53
+
+
 2005-09-03 16:54:10 GMT	Nir Soffer <nirs@freeshell.org>	patch-929
 
     Summary:
--- a/MoinMoin/Page.py	Thu Sep 29 00:09:43 2005 +0000
+++ b/MoinMoin/Page.py	Thu Sep 29 00:36:27 2005 +0000
@@ -1172,13 +1172,14 @@
                     ) % {'pagename': self.formatter.text(self.page_name)})
                     request.write(''.join(pi_formtext))
 
-        # try to load the parser
-        Parser = wikiutil.importPlugin(self.request.cfg, "parser",
-                                       self.pi_format, "Parser")
-        if Parser is None:
-            # default to plain text formatter (i.e. show the page source)
-            del Parser
-            from parser.plain import Parser
+        # Load the parser, or default to plain text parser that will
+        # just show the page raw source.
+        # TODO: do we need this magic? any effect on debugging?
+        try:
+            Parser = wikiutil.importPlugin(self.request.cfg, "parser", 
+                                           self.pi_format, "Parser")
+        except ImportError:
+            from MoinMoin.parser.plain import Parser
 
         # start wiki content div
         request.write(self.formatter.startContent(content_id))
@@ -1258,11 +1259,13 @@
             not self._raw_body_modified and
             self.getFormatterName() in self.cfg.caching_formats):
             # Everything is fine, now check the parser:
-            if not parser:
-                parser = wikiutil.importPlugin(self.request.cfg, "parser",
-                                               self.pi_format, "Parser")
+            if parser is None:
+                try:
+                    parser = wikiutil.importPlugin(self.request.cfg, "parser",
+                                                   self.pi_format, "Parser")
+                except ImportError:
+                    pass
             return getattr(parser, 'caching', False)
-
         return False
 
     def send_page_content(self, request, Parser, body, format_args='',
--- a/MoinMoin/_tests/test_pysupport.py	Thu Sep 29 00:09:43 2005 +0000
+++ b/MoinMoin/_tests/test_pysupport.py	Thu Sep 29 00:36:27 2005 +0000
@@ -18,19 +18,21 @@
     not broken.
     """
 
-    def testNonExisting(self):
-        """ pysupport: import nonexistent moin plugin fail """
-        self.failIf(pysupport.importName('MoinMoin.parser.abcdefghijkl',
-                                         'Parser'))
+    def testNonExistingModule(self):
+        """ pysupport: import nonexistent module raises ImportError """
+        self.assertRaises(ImportError, pysupport.importName,
+                          'MoinMoin.parser.abcdefghijkl','Parser')
+
+    def testNonExistingAttribute(self):
+        """ pysupport: import nonexistent attritbue raises AttributeError """
+        self.assertRaises(AttributeError, pysupport.importName,
+                          'MoinMoin.parser.wiki','NoSuchParser')
 
     def testExisting(self):
-        """ pysupport: import existing moin plugin
-        
-        This tests if a module can be imported from the package
-        MoinMoin. Should never fail!
-        """
-        self.failUnless(pysupport.importName('MoinMoin.parser.wiki',
-                                             'Parser'))
+        """ pysupport: import name from existing module """
+        from MoinMoin.parser import wiki
+        Parser = pysupport.importName('MoinMoin.parser.wiki', 'Parser')
+        self.failUnless(Parser is wiki.Parser)
    
 
 class ImportNameFromPlugin(unittest.TestCase):
@@ -67,7 +69,8 @@
         """ pysupport: import nonexistent wiki plugin fail """
         if self.pluginExists():
             raise TestSkiped('plugin exists: %s' % self.plugin)
-        self.failIf(pysupport.importName(self.pluginModule, self.name))
+        self.assertRaises(ImportError, pysupport.importName,
+                          self.pluginModule, self.name)
 
 
 class ImportExisting(ImportNameFromPlugin):
--- a/MoinMoin/formatter/text_python.py	Thu Sep 29 00:09:43 2005 +0000
+++ b/MoinMoin/formatter/text_python.py	Thu Sep 29 00:36:27 2005 +0000
@@ -20,6 +20,8 @@
         Page must be assembled after the parsing to get working python code.
     """
 
+    defaultDependencies = ["time"]
+    
     def __init__(self, request, static = [], formatter = None, **kw):
         if formatter:
             self.formatter = formatter
@@ -174,19 +176,17 @@
                 (self.__adjust_formatter_state(),
                  self.__formatter, name, args))
             
-    def processor(self, processor_name, lines, is_parser = 0):
+    def processor(self, processor_name, lines, is_parser=0):
         """ processor_name MUST be valid!
         prints out the result insted of returning it!
         """
-        if not is_parser:
-            Dependencies = wikiutil.importPlugin(self.request.cfg, "processor",
-                                                 processor_name, "Dependencies")
-        else:
-            Dependencies = wikiutil.importPlugin(self.request.cfg, "parser",
-                                                 processor_name, "Dependencies")
-            
-        if Dependencies == None:
-            Dependencies = ["time"]
+        type = ["processor", "parser"][is_parser]
+        try:
+            Dependencies = wikiutil.importPlugin(self.request.cfg, type,
+                                                 processor_name,
+                                                 "Dependencies")
+        except AttributeError:
+            Dependencies = self.defaultDependencies
         if self.__is_static(Dependencies):
             return self.formatter.processor(processor_name, lines, is_parser)
         else:
--- a/MoinMoin/i18n/__init__.py	Thu Sep 29 00:09:43 2005 +0000
+++ b/MoinMoin/i18n/__init__.py	Thu Sep 29 00:36:27 2005 +0000
@@ -134,8 +134,10 @@
     if needsupdate:    
         from MoinMoin.util import pysupport
         lang_module = "MoinMoin.i18n." + filename(lang)
-        texts = pysupport.importName(lang_module, "text")
-        if not texts:
+        try:
+            # Language module without text dict will raise AttributeError
+            texts = pysupport.importName(lang_module, "text")
+        except ImportError:
             return (None, None)
         meta = pysupport.importName(lang_module, "meta") 
         encoding = meta['encoding']
--- a/MoinMoin/macro/StatsChart.py	Thu Sep 29 00:09:43 2005 +0000
+++ b/MoinMoin/macro/StatsChart.py	Thu Sep 29 00:36:27 2005 +0000
@@ -21,8 +21,10 @@
                 formatter.text(_('You need to provide a chart type!')) +
                 formatter.sysmsg(0))
 
-    func = pysupport.importName("MoinMoin.stats." + args, "linkto")
-    if not func:
+    try:
+        # stats module without 'linkto' will raise AttributeError
+        func = pysupport.importName("MoinMoin.stats." + args, "linkto")
+    except ImportError:
         return (formatter.sysmsg(1) +
                 formatter.text(_('Bad chart type "%s"!') % args) +
                 formatter.sysmsg(0))
--- a/MoinMoin/parser/wiki.py	Thu Sep 29 00:09:43 2005 +0000
+++ b/MoinMoin/parser/wiki.py	Thu Sep 29 00:36:27 2005 +0000
@@ -766,16 +766,9 @@
             self.in_pre = 3
             return self._closeP() + self.formatter.preformatted(1)
         elif s_word[:2] == '#!':
-            # first try to find a processor for this (will go away in 2.0)
+            # First try to find a processor for this (will go away in 2.0)
             processor_name = s_word[2:].split()[0]
-            self.processor = wikiutil.importPlugin(
-                self.request.cfg, "processor", processor_name, "process")
-            # now look for a parser with that name
-            if self.processor is None:
-                self.processor = wikiutil.importPlugin(
-                    self.request.cfg, "parser", processor_name, "Parser")
-                if self.processor:
-                    self.processor_is_parser = 1
+            self.setProcessor(processor_name)
 
         if self.processor:
             self.processor_name = processor_name
@@ -963,15 +956,8 @@
                     processor_name = ''
                     if (line.strip()[:2] == "#!"):
                         processor_name = line.strip()[2:].split()[0]
-                        self.processor = wikiutil.importPlugin(
-                            self.request.cfg, "processor", processor_name, "process")
-                                                               
-                        # now look for a parser with that name
-                        if self.processor is None:
-                            self.processor = wikiutil.importPlugin(
-                                self.request.cfg, "parser", processor_name, "Parser") 
-                            if self.processor:
-                                self.processor_is_parser = 1
+                        self.setProcessor(processor_name)
+
                     if self.processor:
                         self.in_pre = 2
                         self.colorize_lines = [line]
@@ -1090,4 +1076,20 @@
         if self.formatter.in_p: self.request.write(self.formatter.paragraph(0))
         if self.in_table: self.request.write(self.formatter.table(0))
 
-
+    # --------------------------------------------------------------------
+    # Private helpers
+    
+    def setProcessor(self, name):
+        """ Set processer to either processor or parser named 'name' """
+        cfg = self.request.cfg
+        try:
+            self.processor = wikiutil.importPlugin(cfg, "processor", name,
+                                                   "process")
+            self.processor_is_parser = 0
+        except ImportError:
+            try:
+                self.processor = wikiutil.importPlugin(cfg, "parser", name,
+                                                   "Parser")
+                self.processor_is_parser = 1
+            except ImportError:
+                self.processor = None
--- a/MoinMoin/request.py	Thu Sep 29 00:09:43 2005 +0000
+++ b/MoinMoin/request.py	Thu Sep 29 00:36:27 2005 +0000
@@ -476,16 +476,20 @@
         fallback = 0
         if theme_name == "<default>":
             theme_name = self.cfg.theme_default
-        Theme = wikiutil.importPlugin(self.cfg, 'theme', theme_name, 'Theme')
-        if Theme is None:
+        
+        try:
+            Theme = wikiutil.importPlugin(self.cfg, 'theme', theme_name,
+                                          'Theme')
+        except ImportError:
             fallback = 1
-            Theme = wikiutil.importPlugin(self.cfg, 'theme',
-                                          self.cfg.theme_default, 'Theme')
-            if Theme is None:
+            try:
+                Theme = wikiutil.importPlugin(self.cfg, 'theme',
+                                              self.cfg.theme_default, 'Theme')
+            except ImportError:
                 fallback = 2
                 from MoinMoin.theme.modern import Theme
+        
         self.theme = Theme(self)
-
         return fallback
 
     def setContentLanguage(self, lang):
--- a/MoinMoin/util/pysupport.py	Thu Sep 29 00:09:43 2005 +0000
+++ b/MoinMoin/util/pysupport.py	Thu Sep 29 00:36:27 2005 +0000
@@ -57,17 +57,16 @@
 
     Used to do dynamic import of modules and names that you know their
     names only in runtime.
+
+    Any error raised here must be handled by the caller.
     
     @param modulename: full qualified mudule name, e.g. x.y.z
     @param name: name to import from modulename
     @rtype: any object
-    @return: name from module or None if there is no such name
+    @return: name from module
     """
-    try:
-        module = __import__(modulename, globals(), {}, [name])
-        return getattr(module, name, None)
-    except ImportError:
-        return None
+    module = __import__(modulename, globals(), {}, [name])
+    return getattr(module, name)
 
 
 def makeThreadSafe(function, lock=None):
--- a/MoinMoin/wikiaction.py	Thu Sep 29 00:09:43 2005 +0000
+++ b/MoinMoin/wikiaction.py	Thu Sep 29 00:36:27 2005 +0000
@@ -830,13 +830,14 @@
         mimetype = u"text/plain"
 
     # try to load the formatter
-    Formatter = wikiutil.importPlugin(request.cfg, "formatter",
-        mimetype.translate({ord(u'/'): u'_', ord(u'.'): u'_'}), "Formatter")
-    if Formatter is None:
+    formatterName = mimetype.translate({ord(u'/'): u'_', ord(u'.'): u'_'})
+    try:
+        Formatter = wikiutil.importPlugin(request.cfg, "formatter",
+                                          formatterName, "Formatter")
+    except ImportError:
         # default to plain text formatter
-        del Formatter
         mimetype = "text/plain"
-        from formatter.text_plain import Formatter
+        from MoinMoin.formatter.text_plain import Formatter
 
     if "xml" in mimetype:
         mimetype = "text/xml"
@@ -847,10 +848,31 @@
 
 
 def do_chart(pagename, request):
-    if request.user.may.read(pagename) and request.cfg.chart_options:
-        chart_type = request.form['type'][0]
-        func = pysupport.importName("MoinMoin.stats." + chart_type, "draw")
-        func(pagename, request)
+    """ Show page charts 
+    
+    TODO: add support for text charts?
+    """
+    _ = request.getText
+    if not request.user.may.read(pagename):
+        msg = _("You are not allowed to view this page.")
+        return request.page.send_page(request, msg=msg)
+    
+    if not request.cfg.chart_options:
+        msg = _("Charts are not available!")
+        return request.page.send_page(request, msg=msg)
+    
+    chart_type = request.form.get('type', [''])[0].strip()
+    if not chart_type:
+        msg = _('You need to provide a chart type!')
+        return request.page.send_page(request, msg=msg)
+    
+    try:
+        func = pysupport.importName("MoinMoin.stats." + chart_type, 'draw')
+    except (ImportError, AttributeError):
+        msg = _('Bad chart type "%s"!') % chart_type
+        return request.page.send_page(request, msg=msg)
+    
+    func(pagename, request)
     raise MoinMoinNoFooter
 
 
@@ -929,8 +951,10 @@
     from MoinMoin.formatter.text_html import Formatter
     request.formatter = Formatter(request)
 
-    handler = wikiutil.importPlugin(request.cfg, "action", action, identifier)
-    if handler is None:
+    try:
+        handler = wikiutil.importPlugin(request.cfg, "action", action,
+                                        identifier)
+    except ImportError:
         handler = globals().get('do_' + action)
         
     return handler
--- a/MoinMoin/wikimacro.py	Thu Sep 29 00:09:43 2005 +0000
+++ b/MoinMoin/wikimacro.py	Thu Sep 29 00:36:27 2005 +0000
@@ -61,6 +61,7 @@
      * External macros - implemented in either MoinMoin.macro package, or
        in the specific wiki instance in the plugin/macro directory
     """
+    defaultDependency = ["time"]
 
     Dependencies = {
         "TitleSearch" : ["namespace"],
@@ -94,24 +95,31 @@
         self.formatter = self.request.formatter
         self._ = self.request.getText
         self.cfg = self.request.cfg
+        
+        # Initialized on execute
+        self.name = None
 
     def execute(self, macro_name, args):
-        macro = wikiutil.importPlugin(self.request.cfg, 'macro', macro_name)
-        if macro:
-            return macro(self, args)
+        """ Get and execute a macro 
+        
+        Try to get a plugin macro, or a builtin macro or a language
+        macro, or just raise ImportError. 
+        """
+        self.name = macro_name
+        try:
+            execute = wikiutil.importPlugin(self.cfg, 'macro', macro_name)
+        except ImportError:
+            try:
+                builtins = self.__class__
+                execute = getattr(builtins, '_macro_' + macro_name)
+            except AttributeError:
+                if macro_name in i18n.languages:
+                    execute = builtins._m_lang
+                else:
+                    raise ImportError("Cannot load macro %s" % macro_name)        
+        return execute(self, args)
 
-        builtins = vars(self.__class__)
-        # builtin macro
-        if builtins.has_key('_macro_' + macro_name):
-            return builtins['_macro_' + macro_name](self, args)
-
-        # language pseudo macro
-        if i18n.languages.has_key(macro_name):
-            return self._m_lang(macro_name, args)
-
-        raise ImportError("Cannot load macro %s" % macro_name)
-
-    def _m_lang(self, lang_name, text):
+    def _m_lang(self, text):
         """ Set the current language for page content.
         
             Language macro are used in two ways:
@@ -119,22 +127,21 @@
              * [lang(text)] - insert text with specific lang inside page
         """
         if text:
-            return (self.formatter.lang(1, lang_name) +
+            return (self.formatter.lang(1, self.name) +
                     self.formatter.text(text) +
-                    self.formatter.lang(0, lang_name))
+                    self.formatter.lang(0, self.name))
         
-        self.request.current_lang = lang_name
+        self.request.current_lang = self.name
         return ''
   
     def get_dependencies(self, macro_name):
-        if self.Dependencies.has_key(macro_name):
+        if macro_name in self.Dependencies:
             return self.Dependencies[macro_name]
-        result = wikiutil.importPlugin(self.request.cfg, 'macro', macro_name,
-                                       'Dependencies')
-        if result != None:
-            return result
-        else:
-            return ["time"]
+        try:
+            return wikiutil.importPlugin(self.request.cfg, 'macro',
+                                         macro_name, 'Dependencies')
+        except (ImportError, AttributeError):
+            return self.defaultDependency
 
     def _macro_TitleSearch(self, args):
         return self._m_search("titlesearch")
--- a/MoinMoin/wikirpc.py	Thu Sep 29 00:09:43 2005 +0000
+++ b/MoinMoin/wikirpc.py	Thu Sep 29 00:36:27 2005 +0000
@@ -408,9 +408,10 @@
             try:
                 fn = getattr(self, 'xmlrpc_' + method)
             except AttributeError:
-                fn = wikiutil.importPlugin(self.request.cfg, 'xmlrpc', method,
-                                           'execute')
-                if fn is None:
+                try:
+                    fn = wikiutil.importPlugin(self.request.cfg, 'xmlrpc',
+                                               method, 'execute')
+                except ImportError:
                     response = xmlrpclib.Fault(1, "No such method: %s." %
                                                method)
                 else:
--- a/MoinMoin/wikiutil.py	Thu Sep 29 00:09:43 2005 +0000
+++ b/MoinMoin/wikiutil.py	Thu Sep 29 00:36:27 2005 +0000
@@ -568,8 +568,9 @@
 def importPlugin(cfg, kind, name, function="execute"):
     """ Import wiki or builtin plugin
     
-    Returns an object from a plugin module or None if module or
-    'function' is not found.
+    Returns function from a plugin module. If the module can not be
+    imported, raise ImportError. If function is not there, raise
+    AttributeError.
 
     kind may be one of 'action', 'formatter', 'macro', 'processor',
     'parser' or any other directory that exist in MoinMoin or
@@ -585,18 +586,20 @@
     @rtype: callable
     @return: "function" of module "name" of kind "kind", or None
     """
-    # Try to import from the wiki
-    plugin = importWikiPlugin(cfg, kind, name, function)
-    if plugin is None:
-        # Try to get the plugin from MoinMoin
+    try:
+        plugin = importWikiPlugin(cfg, kind, name, function)
+    except ImportError:
         modulename = 'MoinMoin.%s.%s' % (kind, name)
         plugin = pysupport.importName(modulename, function)
-        
     return plugin
 
 def importWikiPlugin(cfg, kind, name, function):
-    """ Import plugin from the wiki data directory
+    """ Import and cache plugin from the wiki data directory
     
+    Returns function from a plugin module. If the module can not be
+    imported, raise ImportError. If function is not there, raise
+    AttributeError.
+
     We try to import only ONCE - then cache the plugin, even if we got
     None. This way we prevent expensive import of existing plugins for
     each call to a plugin.
@@ -606,28 +609,32 @@
     @param name: the name of the module
     @param function: the function name
     @rtype: callable
-    @return: "function" of module "name" of kind "kind", or None
+    @return: "function" of module "name" of kind "kind"
     """
-
+    try:
+        wikiPlugins = cfg._wiki_plugins
+    except AttributeError:
+        wikiPlugins = cfg._wiki_plugins = {}
+        
     # Wiki plugins are located under 'wikiconfigname.plugin' module.
     modulename = '%s.plugin.%s.%s' % (cfg.siteid, kind, name)
-    key = (modulename, function)
+    # Try cache or import once from disk
     try:
-        # Try cache first - fast!
-        plugin = cfg._wiki_plugins[key]
-    except (KeyError, AttributeError):
-        # Try to import from disk and cache result - slow!
-        plugin = pysupport.importName(modulename, function)
+        module = wikiPlugins[modulename]
+    except KeyError:
         try:
-            cfg._wiki_plugins[key] = plugin
-        except AttributeError:
-            cfg._wiki_plugins = {key: plugin}
-    return plugin
+            module = __import__(modulename, globals(), {}, ['dummy'])
+        except ImportError:
+            module = wikiPlugins[modulename] = None
+    if module is None:
+        raise ImportError
+    return getattr(module, function)
 
 # If we use threads, make this function thread safe
 if config.use_threads:
     importWikiPlugin = pysupport.makeThreadSafe(importWikiPlugin)
 
+
 def builtinPlugins(kind):
     """ Gets a list of modules in MoinMoin.'kind'
     
@@ -636,12 +643,14 @@
     @return: module names
     """
     modulename = "MoinMoin." + kind
-    plugins = pysupport.importName(modulename, "modules")
-    return plugins or []
+    return pysupport.importName(modulename, "modules")
 
 
 def wikiPlugins(kind, cfg):
     """ Gets a list of modules in data/plugin/'kind'
+ 
+    Require valid plugin directory. e.g missing 'parser' directory or
+    missing '__init__.py' file will raise errors.
     
     @param kind: what kind of modules we look for
     @rtype: list
@@ -649,8 +658,7 @@
     """
     # Wiki plugins are located in wikiconfig.plugin module
     modulename = '%s.plugin.%s' % (cfg.siteid, kind)
-    plugins = pysupport.importName(modulename, "modules")
-    return plugins or []
+    return pysupport.importName(modulename, "modules")
 
 
 def getPlugins(kind, cfg):
@@ -692,15 +700,17 @@
         import types
         etp, etd = {}, None
         for pname in getPlugins('parser', cfg):
-            Parser = importPlugin(cfg, 'parser', pname, 'Parser')
-            if Parser is not None:
-                if hasattr(Parser, 'extensions'):
-                    exts = Parser.extensions
-                    if type(exts) == types.ListType:
-                        for ext in Parser.extensions:
-                            etp[ext] = Parser
-                    elif str(exts) == '*':
-                        etd = Parser
+            try:
+                Parser = importPlugin(cfg, 'parser', pname, 'Parser')
+            except ImportError:
+                continue
+            if hasattr(Parser, 'extensions'):
+                exts = Parser.extensions
+                if type(exts) == types.ListType:
+                    for ext in Parser.extensions:
+                        etp[ext] = Parser
+                elif str(exts) == '*':
+                    etd = Parser
         cfg._EXT_TO_PARSER = etp
         cfg._EXT_TO_PARSER_DEFAULT = etd
         
--- a/docs/CHANGES	Thu Sep 29 00:09:43 2005 +0000
+++ b/docs/CHANGES	Thu Sep 29 00:36:27 2005 +0000
@@ -151,6 +151,14 @@
       manual url mapping.
       See HelpOnConfiguration/IntegratingWithApache
 
+Developer notes:
+    
+    * Plugin API was improved. When plugin module is missing, an
+      ImportError is raised. When trying to import a missing name from a
+      plugin module, an AttributeError is raised. You must update any
+      code that use wikiutil.importPlugin or util.pysupport.importName.
+      Errors in your plugin should raise now correct tracebacks.
+      See http://moinmoin.wikiwikiweb.de/ErrorHandlingInPlugins
 
   Internal Changes:
     * request.formatter (html) is available for actions now
@@ -199,6 +207,10 @@
       the user that he entered an unknown email address.
     * Fixed SystemInfo, it now also lists parsers in data/plugin/parser dir.
     * Fix error handling on failure, improved error display
+    * Fix error handling when importing plugins or importing modules
+      dynamically. The fix is not backward compatible with older plugins.
+    * Fix chart action, returns a page with error message when chart
+      can not be created.
     
     
 Version 1.4: