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: John Goerzen <jgoerzen@xxxxxxxxxxxx>
Cc: <offlineimap@xxxxxxxxxxxx>, Christoph Höger <choeger@xxxxxxxxxxxxxxx>
Subject: [PATCH] Prepare dynamic plugins (again)
From: Nicolas Sebrecht <nicolas.s-dev@xxxxxxxxxxx>
Date: Thu, 10 Sep 2009 13:41:16 +0200

From: Christoph Höger <choeger@xxxxxxxxxxxxxxx>

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öger <choeger@xxxxxxxxxxxxxxx>
---

Hi John, here is another test to check what's happening when Christoph send
patches. Again, I'm not the author of this patch.


 HACKING                                        |   11 +++++++
 offlineimap/ui/__init__.py                     |   17 -----------
 offlineimap/ui/detector.py                     |   35 +++++++++++++----------
 offlineimap/ui/{ => plugins}/Blinkenlights.py  |    8 +----
 offlineimap/ui/{ => plugins}/Curses.py         |    6 ++--
 offlineimap/ui/{ => plugins}/Machine.py        |    4 +--
 offlineimap/ui/{ => plugins}/Noninteractive.py |    6 ++--
 offlineimap/ui/{ => plugins}/TTY.py            |    2 +-
 offlineimap/ui/{ => plugins}/__init__.py       |   20 -------------
 setup.py                                       |    3 +-
 10 files changed, 43 insertions(+), 69 deletions(-)
 create mode 100644 HACKING
 rename offlineimap/ui/{ => plugins}/Blinkenlights.py (96%)
 rename offlineimap/ui/{ => plugins}/Curses.py (99%)
 rename offlineimap/ui/{ => plugins}/Machine.py (98%)
 rename offlineimap/ui/{ => plugins}/Noninteractive.py (93%)
 rename offlineimap/ui/{ => plugins}/TTY.py (98%)
 copy offlineimap/ui/{ => plugins}/__init__.py (78%)

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
+======================
+
+You can develop your own plugin by simply subclassing the UIBase class from 
offlineimap.ui.UIBase (have a look at TTYUI for a basic understanding of that).
+In difference to versions <= 6.2.0 you can now easily activate your plugin by 
putting it into the offlineimap/ui/plugins folder. This works without 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 getUIClass() 
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-1301 USA
 
-
-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
 
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
 
-DEFAULT_UI_LIST = ('Curses.Blinkenlights', 'TTY.TTYUI',
-                   'Noninteractive.Basic', 'Noninteractive.Quiet',
-                   'Machine.MachineUI')
-
 def findUI(config, chosenUI=None):
-    uistrlist = list(DEFAULT_UI_LIST)
-    namespace={}
-    for ui in dir(offlineimap.ui):
-        if ui.startswith('_') or ui in ('detector', 'UIBase'):
-            continue
-        namespace[ui]=getattr(offlineimap.ui, ui)
+    uistrlist = ["Curses.Blinkenlights"]
 
     if chosenUI is not None:
         uistrlist = [chosenUI]
@@ -37,7 +28,7 @@ def findUI(config, chosenUI=None):
         uistrlist = config.get("general", "ui").replace(" ", "").split(",")
 
     for uistr in uistrlist:
-        uimod = getUImod(uistr, config.getlocaleval(), namespace)
+        uimod = getUImod(uistr)
         if uimod:
             uiinstance = uimod(config)
             if uiinstance.isusable():
@@ -45,10 +36,24 @@ def findUI(config, chosenUI=None):
     sys.stderr.write("ERROR: No UIs were found usable!\n")
     sys.exit(200)
     
-def getUImod(uistr, localeval, namespace):
+def getUImod(uistr):
+    # ensure backwards compatibility with configs from <= 6.1
+    # uiClassName and dot will be empty if no . is found
+    uimodName,dot,uiClassName=uistr.partition(".")
+    
+    # this asserts that all elements are in place
     try:
-        uimod = localeval.eval(uistr, namespace)
+        # get the module from the plugin path
+        uimod = __import__("offlineimap.ui.plugins." + 
uimodName,[],[],[uimodName])
+        if not uiClassName == "":
+            # if uiClassName is set, we use the old method (introspection
+            # rocks!)
+            uiClass=uimod.__dict__[uiClassName]
+        else:
+            # asking the plugin for the class name is less error prone
+            uiClass = 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/Blinkenlights.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)
             
-    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)
 
     def sleeping(s, sleepsecs, remainingsecs):
         if remainingsecs and s.gettf().getcolor() == '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-1301 USA
 
 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)
 
-    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)
             
 if __name__ == '__main__':
     x = Blinkenlights(None)
diff --git a/offlineimap/ui/Machine.py b/offlineimap/ui/plugins/Machine.py
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 @@
 
 import offlineimap.version
 import urllib, sys, re, time, traceback, threading, thread
-from UIBase import UIBase
+from offlineimap.ui.UIBase import UIBase
 from threading import *
 
 protocol = '6.0.0'
@@ -175,5 +175,3 @@ class MachineUI(UIBase):
     def init_banner(s):
         s._printData('initbanner', offlineimap.version.banner)
 
-    def callhook(s, msg):
-        s._printData('callhook', msg)
diff --git a/offlineimap/ui/Noninteractive.py 
b/offlineimap/ui/plugins/Noninteractive.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-1301 USA
 
 import sys, time
-from UIBase import UIBase
+from offlineimap.ui.UIBase import UIBase
 
 class Basic(UIBase):
     def getpass(s, accountname, config, errmsg = None):
@@ -33,10 +33,10 @@ class Basic(UIBase):
             warntxt = 'warning'
         sys.stderr.write(warntxt + ": " + str(msg) + "\n")
 
-    def sleep(s, sleepsecs, siglistener):
+    def sleep(s, sleepsecs):
         if s.verbose >= 0:
             s._msg("Sleeping for %d:%02d" % (sleepsecs / 60, sleepsecs % 60))
-        return UIBase.sleep(s, sleepsecs, siglistener)
+        UIBase.sleep(s, sleepsecs)
 
     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-1301 USA
 
-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/__init__.py b/offlineimap/ui/plugins/__init__.py
similarity index 78%
copy from offlineimap/ui/__init__.py
copy to offlineimap/ui/plugins/__init__.py
index 0206ab4..1b42165 100644
--- a/offlineimap/ui/__init__.py
+++ b/offlineimap/ui/plugins/__init__.py
@@ -16,23 +16,3 @@
 #    along with this program; if not, write to the Free Software
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 
-
-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
-
diff --git a/setup.py b/setup.py
index 92fb1fd..fe3c53d 100644
--- a/setup.py
+++ b/setup.py
@@ -33,7 +33,8 @@ setup(name = "offlineimap",
       author_email = offlineimap.version.author_email,
       url = offlineimap.version.homepage,
       packages = ['offlineimap', 'offlineimap.folder',
-                  'offlineimap.repository', 'offlineimap.ui'],
+                  'offlineimap.repository',
+                  'offlineimap.ui','offlineimap.ui.plugins'],
       scripts = ['bin/offlineimap'],
       license = offlineimap.version.copyright + \
                 ", Licensed under the GPL version 2"
-- 
1.6.5.rc0.164.g5f6b0




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