Complete.Org: Mailing Lists: Archives: offlineimap: September 2009:
[PATCH] Prepare dynamic plugins (again)
Home

[PATCH] Prepare dynamic plugins (again)

[Top] [All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index] [Thread Index]
To: offlineimap@xxxxxxxxxxxx
Subject: [PATCH] Prepare dynamic plugins (again)
From: Christoph Höger <choeger@xxxxxxxxxxxxxxx>
Date: Mon, 7 Sep 2009 21:42:18 +0200

Here we go again, this time much smaller and without the
GnomeUI.

This patch allows ui plugins to be dropped only
inside the ui/plugins folder and work without modifying
any other file. This should be good for packagers.

* config:
Old config options like "Curses.Blinkenlights" still work
New ui plugins can use Modulename only if they provide a
getUIClass() method, I think thats a compromise

* default ui:
I have made Curses.Blinkenlights the default ui, but
perhaps TTY.TTYUI is a better choice for that.

* testing:
I tested against Blinkenlights, TTY and Gnome ui (which is not
yet in the mainline branch)

Signed-off-by: Christoph H=F6ger <choeger@xxxxxxxxxxxxxxx>
---
 HACKING                                        |   11 +++++++
 offlineimap/ui/__init__.py                     |   17 -----------
 offlineimap/ui/detector.py                     |   35 +++++++++++++-----=
-----
 offlineimap/ui/{ =3D> plugins}/Blinkenlights.py  |    8 +----
 offlineimap/ui/{ =3D> plugins}/Curses.py         |    6 ++--
 offlineimap/ui/{ =3D> plugins}/Machine.py        |    4 +--
 offlineimap/ui/{ =3D> plugins}/Noninteractive.py |    6 ++--
 offlineimap/ui/{ =3D> plugins}/TTY.py            |    2 +-
 offlineimap/ui/plugins/__init__.py             |   18 ++++++++++++
 setup.py                                       |    3 +-
 10 files changed, 61 insertions(+), 49 deletions(-)
 create mode 100644 HACKING
 rename offlineimap/ui/{ =3D> plugins}/Blinkenlights.py (96%)
 rename offlineimap/ui/{ =3D> plugins}/Curses.py (99%)
 rename offlineimap/ui/{ =3D> plugins}/Machine.py (98%)
 rename offlineimap/ui/{ =3D> plugins}/Noninteractive.py (93%)
 rename offlineimap/ui/{ =3D> plugins}/TTY.py (98%)
 create mode 100644 offlineimap/ui/plugins/__init__.py

diff --git a/HACKING b/HACKING
new file mode 100644
index 0000000..8c6fb19
--- /dev/null
+++ b/HACKING
@@ -0,0 +1,11 @@
+Developing your own UI
+=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
+
+You can develop your own plugin by simply subclassing the UIBase class f=
rom offlineimap.ui.UIBase (have a look at TTYUI for a basic understanding=
 of that).
+In difference to versions <=3D 6.2.0 you can now easily activate your pl=
ugin by putting it into the offlineimap/ui/plugins folder. This works wit=
hout any other preparation. So if your module is named foo.py and your UI=
 class is Bar the user can now use it with offlineimap -u foo.Bar
+If you want to make the life of the user even easier, just add a getUICl=
ass() method in your module, let's say:
+
+       # note: you return a class, not the instance!
+       def getUIClass(): return Bar
+
+Then the user can run offlineimap -u foo even without knowing about Bar =
(allowing for multiple UI Classes in one module).
diff --git a/offlineimap/ui/__init__.py b/offlineimap/ui/__init__.py
index 0206ab4..081c1fc 100644
--- a/offlineimap/ui/__init__.py
+++ b/offlineimap/ui/__init__.py
@@ -16,23 +16,6 @@
 #    along with this program; if not, write to the Free Software
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-13=
01 USA
=20
-
-import UIBase, Blinkenlights
-try:
-    import TTY
-except ImportError:
-    pass
-
-try:
-    import curses
-except ImportError:
-    pass
-else:
-    import Curses
-
-import Noninteractive
-import Machine
-
 # Must be last
 import detector
=20
diff --git a/offlineimap/ui/detector.py b/offlineimap/ui/detector.py
index 4ec7503..d73c9d0 100644
--- a/offlineimap/ui/detector.py
+++ b/offlineimap/ui/detector.py
@@ -19,17 +19,8 @@
 import offlineimap.ui
 import sys
=20
-DEFAULT_UI_LIST =3D ('Curses.Blinkenlights', 'TTY.TTYUI',
-                   'Noninteractive.Basic', 'Noninteractive.Quiet',
-                   'Machine.MachineUI')
-
 def findUI(config, chosenUI=3DNone):
-    uistrlist =3D list(DEFAULT_UI_LIST)
-    namespace=3D{}
-    for ui in dir(offlineimap.ui):
-        if ui.startswith('_') or ui in ('detector', 'UIBase'):
-            continue
-        namespace[ui]=3Dgetattr(offlineimap.ui, ui)
+    uistrlist =3D ["Curses.Blinkenlights"]
=20
     if chosenUI is not None:
         uistrlist =3D [chosenUI]
@@ -37,7 +28,7 @@ def findUI(config, chosenUI=3DNone):
         uistrlist =3D config.get("general", "ui").replace(" ", "").split=
(",")
=20
     for uistr in uistrlist:
-        uimod =3D getUImod(uistr, config.getlocaleval(), namespace)
+        uimod =3D getUImod(uistr)
         if uimod:
             uiinstance =3D uimod(config)
             if uiinstance.isusable():
@@ -45,10 +36,24 @@ def findUI(config, chosenUI=3DNone):
     sys.stderr.write("ERROR: No UIs were found usable!\n")
     sys.exit(200)
    =20
-def getUImod(uistr, localeval, namespace):
+def getUImod(uistr):
+    # ensure backwards compatibility with configs from <=3D 6.1
+    # uiClassName and dot will be empty if no . is found
+    uimodName,dot,uiClassName=3Duistr.partition(".")
+   =20
+    # this asserts that all elements are in place
     try:
-        uimod =3D localeval.eval(uistr, namespace)
+        # get the module from the plugin path
+        uimod =3D __import__("offlineimap.ui.plugins." + uimodName,[],[]=
,[uimodName])
+        if not uiClassName =3D=3D "":
+            # if uiClassName is set, we use the old method (introspectio=
n
+            # rocks!)
+            uiClass=3Duimod.__dict__[uiClassName]
+        else:
+            # asking the plugin for the class name is less error prone
+            uiClass =3D uimod.getUIClass()
     except (AttributeError, NameError), e:
-        #raise
+        print e
+       #raise
         return None
-    return uimod
+    return uiClass
diff --git a/offlineimap/ui/Blinkenlights.py b/offlineimap/ui/plugins/Bli=
nkenlights.py
similarity index 96%
rename from offlineimap/ui/Blinkenlights.py
rename to offlineimap/ui/plugins/Blinkenlights.py
index dcc4e01..6982351 100644
--- a/offlineimap/ui/Blinkenlights.py
+++ b/offlineimap/ui/plugins/Blinkenlights.py
@@ -127,15 +127,11 @@ class BlinkenBase:
             return tf
         finally:
             s.tflock.release()
-
-    def callhook(s, msg):
-        s.gettf().setcolor('white')
-        s.__class__.__bases__[-1].callhook(s, msg)
            =20
-    def sleep(s, sleepsecs, siglistener):
+    def sleep(s, sleepsecs):
         s.gettf().setcolor('red')
         s.getaccountframe().startsleep(sleepsecs)
-        return UIBase.sleep(s, sleepsecs, siglistener)
+        UIBase.sleep(s, sleepsecs)
=20
     def sleeping(s, sleepsecs, remainingsecs):
         if remainingsecs and s.gettf().getcolor() =3D=3D 'black':
diff --git a/offlineimap/ui/Curses.py b/offlineimap/ui/plugins/Curses.py
similarity index 99%
rename from offlineimap/ui/Curses.py
rename to offlineimap/ui/plugins/Curses.py
index 8eb709f..949edb8 100644
--- a/offlineimap/ui/Curses.py
+++ b/offlineimap/ui/plugins/Curses.py
@@ -17,7 +17,7 @@
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-13=
01 USA
=20
 from Blinkenlights import BlinkenBase
-from UIBase import UIBase
+from offlineimap.ui.UIBase import UIBase
 from threading import *
 import thread, time, sys, os, signal, time
 from offlineimap import version, threadutil
@@ -541,10 +541,10 @@ class Blinkenlights(BlinkenBase, UIBase):
         s.c.stop()
         UIBase.mainException(s)
=20
-    def sleep(s, sleepsecs, siglistener):
+    def sleep(s, sleepsecs):
         s.gettf().setcolor('red')
         s._msg("Next sync in %d:%02d" % (sleepsecs / 60, sleepsecs % 60)=
)
-        return BlinkenBase.sleep(s, sleepsecs, siglistener)
+        BlinkenBase.sleep(s, sleepsecs)
            =20
 if __name__ =3D=3D '__main__':
     x =3D Blinkenlights(None)
diff --git a/offlineimap/ui/Machine.py b/offlineimap/ui/plugins/Machine.p=
y
similarity index 98%
rename from offlineimap/ui/Machine.py
rename to offlineimap/ui/plugins/Machine.py
index 0a07e3e..c77a3c5 100644
--- a/offlineimap/ui/Machine.py
+++ b/offlineimap/ui/plugins/Machine.py
@@ -17,7 +17,7 @@
=20
 import offlineimap.version
 import urllib, sys, re, time, traceback, threading, thread
-from UIBase import UIBase
+from offlineimap.ui.UIBase import UIBase
 from threading import *
=20
 protocol =3D '6.0.0'
@@ -175,5 +175,3 @@ class MachineUI(UIBase):
     def init_banner(s):
         s._printData('initbanner', offlineimap.version.banner)
=20
-    def callhook(s, msg):
-        s._printData('callhook', msg)
diff --git a/offlineimap/ui/Noninteractive.py b/offlineimap/ui/plugins/No=
ninteractive.py
similarity index 93%
rename from offlineimap/ui/Noninteractive.py
rename to offlineimap/ui/plugins/Noninteractive.py
index 9cd5eca..ca8ff5a 100644
--- a/offlineimap/ui/Noninteractive.py
+++ b/offlineimap/ui/plugins/Noninteractive.py
@@ -17,7 +17,7 @@
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-13=
01 USA
=20
 import sys, time
-from UIBase import UIBase
+from offlineimap.ui.UIBase import UIBase
=20
 class Basic(UIBase):
     def getpass(s, accountname, config, errmsg =3D None):
@@ -33,10 +33,10 @@ class Basic(UIBase):
             warntxt =3D 'warning'
         sys.stderr.write(warntxt + ": " + str(msg) + "\n")
=20
-    def sleep(s, sleepsecs, siglistener):
+    def sleep(s, sleepsecs):
         if s.verbose >=3D 0:
             s._msg("Sleeping for %d:%02d" % (sleepsecs / 60, sleepsecs %=
 60))
-        return UIBase.sleep(s, sleepsecs, siglistener)
+        UIBase.sleep(s, sleepsecs)
=20
     def sleeping(s, sleepsecs, remainingsecs):
         if sleepsecs > 0:
diff --git a/offlineimap/ui/TTY.py b/offlineimap/ui/plugins/TTY.py
similarity index 98%
rename from offlineimap/ui/TTY.py
rename to offlineimap/ui/plugins/TTY.py
index 99c46d4..32d6279 100644
--- a/offlineimap/ui/TTY.py
+++ b/offlineimap/ui/plugins/TTY.py
@@ -16,7 +16,7 @@
 #    along with this program; if not, write to the Free Software
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-13=
01 USA
=20
-from UIBase import UIBase
+from offlineimap.ui.UIBase import UIBase
 from getpass import getpass
 import select, sys
 from threading import *
diff --git a/offlineimap/ui/plugins/__init__.py b/offlineimap/ui/plugins/=
__init__.py
new file mode 100644
index 0000000..1b42165
--- /dev/null
+++ b/offlineimap/ui/plugins/__init__.py
@@ -0,0 +1,18 @@
+# UI module directory
+# Copyright (C) 2002 John Goerzen
+# <jgoerzen@xxxxxxxxxxxx>
+#
+#    This program is free software; you can redistribute it and/or modif=
y
+#    it under the terms of the GNU General Public License as published b=
y
+#    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., 51 Franklin St, Fifth Floor, Boston, MA  02110-13=
01 USA
+
diff --git a/setup.py b/setup.py
index 92fb1fd..fe3c53d 100644
--- a/setup.py
+++ b/setup.py
@@ -33,7 +33,8 @@ setup(name =3D "offlineimap",
       author_email =3D offlineimap.version.author_email,
       url =3D offlineimap.version.homepage,
       packages =3D ['offlineimap', 'offlineimap.folder',
-                  'offlineimap.repository', 'offlineimap.ui'],
+                  'offlineimap.repository',
+                  'offlineimap.ui','offlineimap.ui.plugins'],
       scripts =3D ['bin/offlineimap'],
       license =3D offlineimap.version.copyright + \
                 ", Licensed under the GPL version 2"
--=20
1.6.2.5




[Prev in Thread] Current Thread [Next in Thread]