Git Repositories

Externalize some libs: pylibsalt and urwidm.
authorCyrille Pontvieux <jrd@enialis.net>
Wed, 4 Jun 2014 17:09:04 +0000 (19:09 +0200)
committerCyrille Pontvieux <jrd@enialis.net>
Wed, 4 Jun 2014 17:09:04 +0000 (19:09 +0200)
30 files changed:
requirements.txt [new file with mode: 0644]
src/bootsetup.py
src/lib/__init__.py
src/lib/bootsetup_curses.py
src/lib/bootsetup_gtk.py
src/lib/config.py
src/lib/gathercurses.py
src/lib/gathergui.py
src/lib/grub2.py
src/lib/lilo.py
src/lib/salix_livetools_library/__init__.py [deleted file]
src/lib/salix_livetools_library/assertPlus.py [deleted file]
src/lib/salix_livetools_library/bootloader.py [deleted file]
src/lib/salix_livetools_library/chroot.py [deleted file]
src/lib/salix_livetools_library/disk.py [deleted file]
src/lib/salix_livetools_library/execute.py [deleted file]
src/lib/salix_livetools_library/freesize.py [deleted file]
src/lib/salix_livetools_library/fs.py [deleted file]
src/lib/salix_livetools_library/fstab.py [deleted file]
src/lib/salix_livetools_library/kernel.py [deleted file]
src/lib/salix_livetools_library/keyboard.py [deleted file]
src/lib/salix_livetools_library/language.py [deleted file]
src/lib/salix_livetools_library/mounting.py [deleted file]
src/lib/salix_livetools_library/salt.py [deleted file]
src/lib/salix_livetools_library/timezone.py [deleted file]
src/lib/salix_livetools_library/user.py [deleted file]
src/lib/testurwidmore.py [deleted file]
src/lib/urwid_more.py [deleted file]
src/tests/combobox.py [deleted file]
tox.ini [new file with mode: 0644]

diff --git a/requirements.txt b/requirements.txt
new file mode 100644 (file)
index 0000000..ad75cec
--- /dev/null
@@ -0,0 +1,2 @@
+pylibsalt>=0.1.0
+urwidm>=0.1.0
index bf6eb33..8a31ffb 100755 (executable)
@@ -1,11 +1,11 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
+# coding: utf-8
+# vim:et:sta:sts=2:sw=2:ts=2:tw=0:
 """
 BootSetup helps installing LiLo or Grub2 on your computer.
 This is the launcher.
 """
-from __future__ import unicode_literals
+from __future__ import unicode_literals, print_function, division, absolute_import
 
 __app__ = 'bootsetup'
 __copyright__ = 'Copyright 2013-2014, Salix OS'
@@ -19,8 +19,10 @@ __version__ = '0.1'
 import abc
 import os
 import sys
+_ = None  # for jslint
 import gettext
 
+
 class BootSetup:
 
   __metaclass__ = abc.ABCMeta
@@ -33,7 +35,7 @@ class BootSetup:
     self._targetPartition = targetPartition
     self._isTest = isTest
     self._useTestData = useTestData
-    print "BootSetup v{ver}".format(ver = version)
+    print("BootSetup v{ver}".format(ver=version))
 
   @abc.abstractmethod
   def run_setup(self):
@@ -43,7 +45,7 @@ class BootSetup:
     raise NotImplementedError()
 
   @abc.abstractmethod
-  def info_dialog(self, message, title = None, parent = None):
+  def info_dialog(self, message, title=None, parent=None):
     """
     Displays an information message.
 
@@ -51,14 +53,15 @@ class BootSetup:
     raise NotImplementedError()
 
   @abc.abstractmethod
-  def error_dialog(self, message, title = None, parent = None):
+  def error_dialog(self, message, title=None, parent=None):
     """
     Displays an error message.
     """
     return result_error
 
+
 def usage():
-  print """BootSetup v{ver}
+  print("""BootSetup v{ver}
 {copyright}
 {license}
 {author}
@@ -76,16 +79,19 @@ Parameters:
     The partition will be guessed by default if not specified:
       ⋅ First Linux selected partition of the selected disk for LiLo.
       ⋅ First Linux partition, in order, of the selected disk for Grub2. This could be changed in the UI.
-""".format(ver = __version__, copyright = __copyright__, license = __license__, author = __author__)
+""".format(ver=__version__, copyright=__copyright__, license=__license__, author=__author__))
+
 
 def print_err(*args):
   sys.stderr.write((' '.join(map(unicode, args)) + "\n").encode('utf-8'))
 
-def die(s, exit = 1):
+
+def die(s, exit=1):
   print_err(s)
   if exit:
     sys.exit(exit)
 
+
 if __name__ == '__main__':
   if os.path.dirname(__file__):
     os.chdir(os.path.dirname(__file__))
@@ -96,13 +102,13 @@ if __name__ == '__main__':
   target_partition = None
   locale_dir = '/usr/share/locale'
   gettext.install(__app__, locale_dir, True)
-  for arg in sys.argv[1:]: # argv[0] = own name
+  for arg in sys.argv[1:]:  # argv[0] = own name
     if arg:
       if arg == '--help':
         usage()
         sys.exit(0)
       elif arg == '--version':
-        print __version__
+        print(__version__)
         sys.exit(0)
       elif arg == '--test':
         is_test = True
index ca13bf5..6d9ce8f 100644 (file)
@@ -1,10 +1,10 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
+# coding: utf-8
+# vim:et:sta:sts=2:sw=2:ts=2:tw=0:
 """
 BootSetup helps installing LiLo or Grub2 on your computer.
 """
-from __future__ import unicode_literals
+from __future__ import unicode_literals, print_function, division, absolute_import
 
 __copyright__ = 'Copyright 2013-2014, Salix OS'
 __license__ = 'GPL2+'
index b0e3a65..7e00817 100644 (file)
@@ -1,19 +1,18 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
+# coding: utf-8
+# vim:et:sta:sts=2:sw=2:ts=2:tw=0:
 """
 Curses BootSetup.
 """
-from __future__ import unicode_literals
+from __future__ import unicode_literals, print_function, division, absolute_import
 
 __copyright__ = 'Copyright 2013-2014, Salix OS'
 __license__ = 'GPL2+'
 
 import os
 import sys
-import gettext
-import re
-import urwid_more as urwidm
+import gettext  # noqa
+import urwidm
 from gathercurses import *
 from bootsetup import *
 
index e86f488..b01b51b 100644 (file)
@@ -1,17 +1,17 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
+# coding: utf-8
+# vim:et:sta:sts=2:sw=2:ts=2:tw=0:
 """
 Graphical BootSetup.
 """
-from __future__ import unicode_literals
+from __future__ import unicode_literals, print_function, division, absolute_import
 
 __copyright__ = 'Copyright 2013-2014, Salix OS'
 __license__ = 'GPL2+'
 
 import os
 import sys
-import gettext
+import gettext  # noqa
 import gtk
 import gtk.glade
 from bootsetup import *
index 39b9214..c15f0d3 100644 (file)
@@ -1,10 +1,10 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
+# coding: utf-8
+# vim:et:sta:sts=2:sw=2:ts=2:tw=0:
 """
 Config class helps storing the configuration for the bootloader setup.
 """
-from __future__ import unicode_literals
+from __future__ import unicode_literals, print_function, division, absolute_import
 
 __copyright__ = 'Copyright 2013-2014, Salix OS'
 __license__ = 'GPL2+'
@@ -13,7 +13,7 @@ import sys
 import re
 import codecs
 import os
-import salix_livetools_library as sltl
+import libsalt as slt
 
 class Config:
   """
@@ -39,23 +39,23 @@ class Config:
 
   def __debug(self, msg):
     if self.is_test:
-      print "Debug: " + msg
+      print("Debug: " + msg)
       with codecs.open("bootsetup.log", "a+", "utf-8") as fdebug:
         fdebug.write("Debug: {0}\n".format(msg))
 
   def _get_current_config(self):
-    print 'Gathering current configuration…',
+    print('Gathering current configuration…', end='')
     if self.is_test:
-      print ''
+      print('')
     sys.stdout.flush()
     if self.is_test:
       self.is_live = False
     else:
-      self.is_live = sltl.isSaLTLiveEnv()
+      self.is_live = slt.isSaLTLiveEnv()
     if self.use_test_data:
       self.disks = [
-            ['sda', 'WDC100 (100GB)'],
-            ['sdb', 'SGT350 (350GB)']
+            ['sda', 'msdos', 'WDC100 (100GB)'],
+            ['sdb', 'gpt', 'SGT350 (350GB)']
           ]
       self.partitions = [
             ['sda1', 'ntfs', 'WinVista (20GB)'],
@@ -73,18 +73,18 @@ class Config:
     else:
       self.disks = []
       self.partitions = []
-      for disk_device in sltl.getDisks():
-        di = sltl.getDiskInfo(disk_device)
-        self.disks.append([disk_device, "{0} ({1})".format(di['model'], di['sizeHuman'])])
-        for p in sltl.getPartitions(disk_device):
-          pi = sltl.getPartitionInfo(p)
+      for disk_device in slt.getDisks():
+        di = slt.getDiskInfo(disk_device)
+        self.disks.append([disk_device, di['type'], "{0} ({1})".format(di['model'], di['sizeHuman'])])
+        for p in slt.getPartitions(disk_device):
+          pi = slt.getPartitionInfo(p)
           self.partitions.append([p, pi['fstype'], "{0} ({1})".format(pi['label'], pi['sizeHuman'])])
       self.boot_partitions = []
       probes = []
       if not self.is_live:
         # os-prober doesn't want to probe for /
-        slashDevice = sltl.execGetOutput(r"readlink -f $(df / | tail -n 1 | cut -d' ' -f1)")[0]
-        slashFS = sltl.getFsType(re.sub(r'^/dev/', '', slashDevice))
+        slashDevice = slt.execGetOutput(r"readlink -f $(df / | tail -n 1 | cut -d' ' -f1)")[0]
+        slashFS = slt.getFsType(re.sub(r'^/dev/', '', slashDevice))
         osProbesPath = None
         for p in ("/usr/lib64/os-probes/mounted/90linux-distro", "/usr/lib/os-probes/mounted/90linux-distro"):
           if os.path.exists(p):
@@ -97,7 +97,7 @@ class Config:
             pass
           self.__debug("Root device {0} ({1})".format(slashDevice, slashFS))
           self.__debug(osProbesPath + " " + slashDevice + " / " + slashFS)
-          slashDistro = sltl.execGetOutput([osProbesPath, slashDevice, '/', slashFS])
+          slashDistro = slt.execGetOutput([osProbesPath, slashDevice, '/', slashFS])
           if slashDistro:
             probes = slashDistro
       self.__debug("Probes: " + unicode(probes))
@@ -107,7 +107,7 @@ class Config:
           osProberPath = p
           break
       if osProberPath:
-        probes.extend(sltl.execGetOutput(osProberPath, shell = False))
+        probes.extend(slt.execGetOutput(osProberPath, shell = False))
       self.__debug("Probes: " + unicode(probes))
       for probe in probes:
         probe = unicode(probe).strip() # ensure clean line
@@ -131,5 +131,5 @@ class Config:
     elif len(self.disks) > 0:
       # use the first disk.
       self.cur_mbr_device = self.disks[0][0]
-    print ' Done'
+    print(' Done')
     sys.stdout.flush()
index a3194b7..e9bebd0 100644 (file)
@@ -1,24 +1,21 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
+# coding: utf-8
+# vim:et:sta:sts=2:sw=2:ts=2:tw=0:
 """
 Curses (urwid) BootSetup configuration gathering.
 """
-from __future__ import unicode_literals
+from __future__ import unicode_literals, print_function, division, absolute_import
 
 __copyright__ = 'Copyright 2013-2014, Salix OS'
 __license__ = 'GPL2+'
 
-import gettext
-import gobject
-import urwid_more as urwidm
+import gettext  # noqa
+import urwidm
 import re
-import math
-import subprocess
-from config import *
-import salix_livetools_library as sltl
-from lilo import *
-from grub2 import *
+import libsalt as slt
+from .config import *
+from .lilo import *
+from .grub2 import *
 
 class GatherCurses:
   """
@@ -67,14 +64,14 @@ class GatherCurses:
     self._bootsetup = bootsetup
     self._version = version
     self.cfg = Config(bootloader, target_partition, is_test, use_test_data)
-    print """
+    print("""
 bootloader         = {bootloader}
 target partition   = {partition}
 MBR device         = {mbr}
 disks:{disks}
 partitions:{partitions}
 boot partitions:{boot_partitions}
-""".format(bootloader = self.cfg.cur_bootloader, partition = self.cfg.cur_boot_partition, mbr = self.cfg.cur_mbr_device, disks = "\n - " + "\n - ".join(map(" ".join, self.cfg.disks)), partitions = "\n - " + "\n - ".join(map(" ".join, self.cfg.partitions)), boot_partitions = "\n - " + "\n - ".join(map(" ".join, self.cfg.boot_partitions)))
+""".format(bootloader = self.cfg.cur_bootloader, partition = self.cfg.cur_boot_partition, mbr = self.cfg.cur_mbr_device, disks = "\n - " + "\n - ".join(map(" ".join, self.cfg.disks)), partitions = "\n - " + "\n - ".join(map(" ".join, self.cfg.partitions)), boot_partitions = "\n - " + "\n - ".join(map(" ".join, self.cfg.boot_partitions))))
     self.ui = urwidm.raw_display.Screen()
     self.ui.set_mouse_tracking()
     self._palette.extend(bootsetup._palette)
@@ -526,7 +523,7 @@ click on this button to install your bootloader.")
       launched = False
       for editor in self._editors:
         try:
-          sltl.execCall([editor, lilocfg], shell=True, env=None)
+          slt.execCall([editor, lilocfg], shell=True, env=None)
           launched = True
           break
         except:
@@ -568,15 +565,15 @@ click on this button to install your bootloader.")
   def _updateGrub2EditButton(self, doTest = True):
     if doTest:
       partition = os.path.join("/dev", self.cfg.cur_boot_partition)
-      if sltl.isMounted(partition):
-        mp = sltl.getMountPoint(partition)
+      if slt.isMounted(partition):
+        mp = slt.getMountPoint(partition)
         doumount = False
       else:
-        mp = sltl.mountDevice(partition)
+        mp = slt.mountDevice(partition)
         doumount = True
       self._grub2_conf = os.path.exists(os.path.join(mp, "etc/default/grub"))
       if doumount:
-        sltl.umountDevice(mp)
+        slt.umountDevice(mp)
     else:
       self._grub2_conf = False
     self._grub2BtnEdit.sensitive = self._grub2_conf
@@ -584,17 +581,17 @@ click on this button to install your bootloader.")
 
   def _editGrub2Conf(self, button):
     partition = os.path.join("/dev", self.cfg.cur_boot_partition)
-    if sltl.isMounted(partition):
-      mp = sltl.getMountPoint(partition)
+    if slt.isMounted(partition):
+      mp = slt.getMountPoint(partition)
       doumount = False
     else:
-      mp = sltl.mountDevice(partition)
+      mp = slt.mountDevice(partition)
       doumount = True
     grub2cfg = os.path.join(mp, "etc/default/grub")
     launched = False
     for editor in self._editors:
       try:
-        sltl.execCall([editor, grub2cfg], shell=True, env=None)
+        slt.execCall([editor, grub2cfg], shell=True, env=None)
         launched = True
         break
       except:
@@ -602,7 +599,7 @@ click on this button to install your bootloader.")
     if not launched:
       self._errorDialog(_("Sorry, BootSetup is unable to find a suitable text editor in your system. You will not be able to manually modify the Grub2 default configuration.\n"))
     if doumount:
-      sltl.umountDevice(mp)
+      slt.umountDevice(mp)
 
   def _onInstall(self, btnInstall):
     if self.cfg.cur_bootloader == 'lilo':
@@ -614,7 +611,7 @@ click on this button to install your bootloader.")
     self.installation_done()
 
   def installation_done(self):
-    print "Bootloader Installation Done."
+    print("Bootloader Installation Done.")
     msg = _("Bootloader installation process completed.")
     self._infoDialog(msg)
     self.main_quit()
@@ -624,5 +621,5 @@ click on this button to install your bootloader.")
       del self._lilo
     if self._grub2:
       del self._grub2
-    print "Bye _o/"
+    print("Bye _o/")
     raise urwidm.ExitMainLoop()
index b8db8f0..232a28e 100644 (file)
@@ -1,25 +1,23 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
+# coding: utf-8
+# vim:et:sta:sts=2:sw=2:ts=2:tw=0:
 """
 Graphical BootSetup configuration gathering.
 """
-from __future__ import unicode_literals
+from __future__ import unicode_literals, print_function, division, absolute_import
 
 __copyright__ = 'Copyright 2013-2014, Salix OS'
 __license__ = 'GPL2+'
 
-import gettext
+import gettext  # noqa
 import gobject
 import gtk
 import gtk.glade
 import re
-import math
-import subprocess
-from config import *
-import salix_livetools_library as sltl
-from lilo import *
-from grub2 import *
+import libsalt as slt
+from .config import *
+from .lilo import *
+from .grub2 import *
 
 class GatherGui:
   """
@@ -35,14 +33,14 @@ class GatherGui:
   def __init__(self, bootsetup, version, bootloader = None, target_partition = None, is_test = False, use_test_data = False):
     self._bootsetup = bootsetup
     self.cfg = Config(bootloader, target_partition, is_test, use_test_data)
-    print """
+    print("""
 bootloader         = {bootloader}
 target partition   = {partition}
 MBR device         = {mbr}
 disks:{disks}
 partitions:{partitions}
 boot partitions:{boot_partitions}
-""".format(bootloader = self.cfg.cur_bootloader, partition = self.cfg.cur_boot_partition, mbr = self.cfg.cur_mbr_device, disks = "\n - " + "\n - ".join(map(" ".join, self.cfg.disks)), partitions = "\n - " + "\n - ".join(map(" ".join, self.cfg.partitions)), boot_partitions = "\n - " + "\n - ".join(map(" ".join, self.cfg.boot_partitions)))
+""".format(bootloader = self.cfg.cur_bootloader, partition = self.cfg.cur_boot_partition, mbr = self.cfg.cur_mbr_device, disks = "\n - " + "\n - ".join(map(" ".join, self.cfg.disks)), partitions = "\n - " + "\n - ".join(map(" ".join, self.cfg.partitions)), boot_partitions = "\n - " + "\n - ".join(map(" ".join, self.cfg.boot_partitions))))
     builder = gtk.Builder()
     for d in ('./resources', '../resources'):
       if os.path.exists(d + '/bootsetup.glade'):
@@ -178,7 +176,7 @@ click on this button to install your bootloader."))
 
 
   def build_data_stores(self):
-    print 'Building choice lists…',
+    print('Building choice lists…', end='')
     sys.stdout.flush()
     if self.cfg.cur_bootloader == 'lilo':
       self.RadioLilo.activate()
@@ -212,7 +210,7 @@ click on this button to install your bootloader."))
     self.LabelCellRendererCombo.set_property('text-column', 0)
     self.LabelCellRendererCombo.set_property('editable', True)
     self.LabelCellRendererCombo.set_property('cell_background', '#CCCCCC')
-    print ' Done'
+    print(' Done')
     sys.stdout.flush()
 
   # What to do when BootSetup logo is clicked
@@ -230,7 +228,7 @@ click on this button to install your bootloader."))
       del self._lilo
     if self._grub2:
       del self._grub2
-    print "Bye _o/"
+    print("Bye _o/")
     gtk.main_quit()
 
   def process_gui_events(self):
@@ -384,7 +382,7 @@ click on this button to install your bootloader."))
       for editor in self._editors:
         try:
           cmd = editor.split(' ') + [lilocfg]
-          sltl.execCall(cmd, shell=True, env=None)
+          slt.execCall(cmd, shell=True, env=None)
           launched = True
           break
         except:
@@ -406,11 +404,11 @@ click on this button to install your bootloader."))
   
   def on_grub2_edit_button_clicked(self, widget, data=None):
     partition = os.path.join("/dev", self.cfg.cur_boot_partition)
-    if sltl.isMounted(partition):
-      mp = sltl.getMountPoint(partition)
+    if slt.isMounted(partition):
+      mp = slt.getMountPoint(partition)
       doumount = False
     else:
-      mp = sltl.mountDevice(partition)
+      mp = slt.mountDevice(partition)
       doumount = True
     grub2cfg = os.path.join(mp, "etc/default/grub")
     if os.path.exists(grub2cfg):
@@ -418,7 +416,7 @@ click on this button to install your bootloader."))
       for editor in self._editors:
         try:
           cmd = editor.split(' ') + [grub2cfg]
-          sltl.execCall(cmd, shell=True, env=None)
+          slt.execCall(cmd, shell=True, env=None)
           launched = True
           break
         except:
@@ -426,13 +424,13 @@ click on this button to install your bootloader."))
       if not launched:
         self._bootsetup.error_dialog(_("Sorry, BootSetup is unable to find a suitable text editor in your system. You will not be able to manually modify the Grub2 default configuration.\n"))
     if doumount:
-      sltl.umountDevice(mp)
+      slt.umountDevice(mp)
 
   def update_buttons(self):
     install_ok = False
     multiple = False
     grub2_edit_ok = False
-    if self.cfg.cur_mbr_device and os.path.exists("/dev/{0}".format(self.cfg.cur_mbr_device)) and sltl.getDiskInfo(self.cfg.cur_mbr_device):
+    if self.cfg.cur_mbr_device and os.path.exists("/dev/{0}".format(self.cfg.cur_mbr_device)) and slt.getDiskInfo(self.cfg.cur_mbr_device):
       if self.cfg.cur_bootloader == 'lilo' and not self._editing:
         if len(self.BootPartitionListStore) > 1:
           multiple = True
@@ -440,19 +438,19 @@ click on this button to install your bootloader."))
           if bp[4] == "gtk-yes":
             install_ok = True
       elif self.cfg.cur_bootloader == 'grub2':
-        if self.cfg.cur_boot_partition and os.path.exists("/dev/{0}".format(self.cfg.cur_boot_partition)) and sltl.getPartitionInfo(self.cfg.cur_boot_partition):
+        if self.cfg.cur_boot_partition and os.path.exists("/dev/{0}".format(self.cfg.cur_boot_partition)) and slt.getPartitionInfo(self.cfg.cur_boot_partition):
           install_ok = True
         if install_ok:
           partition = os.path.join("/dev", self.cfg.cur_boot_partition)
-          if sltl.isMounted(partition):
-            mp = sltl.getMountPoint(partition)
+          if slt.isMounted(partition):
+            mp = slt.getMountPoint(partition)
             doumount = False
           else:
-            mp = sltl.mountDevice(partition)
+            mp = slt.mountDevice(partition)
             doumount = True
           grub2_edit_ok = os.path.exists(os.path.join(mp, "etc/default/grub"))
           if doumount:
-            sltl.umountDevice(mp)
+            slt.umountDevice(mp)
     self.RadioLilo.set_sensitive(not self._editing)
     self.RadioGrub2.set_sensitive(not self._editing)
     self.ComboBoxMbr.set_sensitive(not self._editing)
@@ -474,7 +472,7 @@ click on this button to install your bootloader."))
     self.installation_done()
 
   def installation_done(self):
-    print "Bootloader Installation Done."
+    print("Bootloader Installation Done.")
     msg = "<b>{0}</b>".format(_("Bootloader installation process completed."))
     self._bootsetup.info_dialog(msg)
     self.gtk_main_quit(self.Window)
index dc074b3..c7f1f76 100644 (file)
@@ -1,10 +1,10 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
+# coding: utf-8
+# vim:et:sta:sts=2:sw=2:ts=2:tw=0:
 """
 Grub2 for BootSetup.
 """
-from __future__ import unicode_literals
+from __future__ import unicode_literals, print_function, division, absolute_import
 
 __copyright__ = 'Copyright 2013-2014, Salix OS'
 __license__ = 'GPL2+'
@@ -13,7 +13,7 @@ import tempfile
 import os
 import sys
 import codecs
-import salix_livetools_library as sltl
+import libsalt as slt
 
 class Grub2:
   
@@ -27,16 +27,16 @@ class Grub2:
     self.isTest = isTest
     self._prefix = "bootsetup.grub2-"
     self._tmp = tempfile.mkdtemp(prefix = self._prefix)
-    sltl.mounting._tempMountDir = os.path.join(self._tmp, 'mounts')
+    slt.mounting._tempMountDir = os.path.join(self._tmp, 'mounts')
     self.__debug("tmp dir = " + self._tmp)
 
   def __del__(self):
     if self._tmp and os.path.exists(self._tmp):
       self.__debug("cleanning " + self._tmp)
       try:
-        if os.path.exists(sltl.mounting._tempMountDir):
-          self.__debug("Remove " + sltl.mounting._tempMountDir)
-          os.rmdir(sltl.mounting._tempMountDir)
+        if os.path.exists(slt.mounting._tempMountDir):
+          self.__debug("Remove " + slt.mounting._tempMountDir)
+          os.rmdir(slt.mounting._tempMountDir)
         self.__debug("Remove " + self._tmp)
         os.rmdir(self._tmp)
       except:
@@ -44,7 +44,7 @@ class Grub2:
 
   def __debug(self, msg):
     if self.isTest:
-      print "Debug: " + msg
+      print("Debug: " + msg)
       with codecs.open("bootsetup.log", "a+", "utf-8") as fdebug:
         fdebug.write("Debug: {0}\n".format(msg))
 
@@ -53,12 +53,12 @@ class Grub2:
     Return the mount point
     """
     self.__debug("bootPartition = " + bootPartition)
-    if sltl.isMounted(bootPartition):
+    if slt.isMounted(bootPartition):
       self.__debug("bootPartition already mounted")
-      return sltl.getMountPoint(bootPartition)
+      return slt.getMountPoint(bootPartition)
     else:
       self.__debug("bootPartition not mounted")
-      return sltl.mountDevice(bootPartition)
+      return slt.mountDevice(bootPartition)
 
   def _mountBootInBootPartition(self, mountPoint):
     # assume that if the mount_point is /, any /boot directory is already accessible/mounted
@@ -66,7 +66,7 @@ class Grub2:
       self.__debug("mp != / and etc/fstab exists, will try to mount /boot by chrooting")
       try:
         self.__debug("grep -q /boot {mp}/etc/fstab && chroot {mp} /sbin/mount /boot".format(mp = mountPoint))
-        if sltl.execCall("grep -q /boot {mp}/etc/fstab && chroot {mp} /sbin/mount /boot".format(mp = mount_point)):
+        if slt.execCall("grep -q /boot {mp}/etc/fstab && chroot {mp} /sbin/mount /boot".format(mp = mount_point)):
           self.__debug("/boot mounted in " + mp)
           self._bootInBootMounted = True
       except:
@@ -79,9 +79,9 @@ class Grub2:
     if mountPoint != "/":
       self.__debug("mount point ≠ / so mount /dev, /proc and /sys in " + mountPoint)
       self._procInBootMounted = True
-      sltl.execCall('mount -o bind /dev {mp}/dev'.format(mp = mountPoint))
-      sltl.execCall('mount -o bind /proc {mp}/proc'.format(mp = mountPoint))
-      sltl.execCall('mount -o bind /sys {mp}/sys'.format(mp = mountPoint))
+      slt.execCall('mount -o bind /dev {mp}/dev'.format(mp = mountPoint))
+      slt.execCall('mount -o bind /proc {mp}/proc'.format(mp = mountPoint))
+      slt.execCall('mount -o bind /sys {mp}/sys'.format(mp = mountPoint))
 
   def _unbindProcSysDev(self, mountPoint):
     """
@@ -89,16 +89,16 @@ class Grub2:
     """
     if self._procInBootMounted:
       self.__debug("mount point ≠ / so umount /dev, /proc and /sys in " + mountPoint)
-      sltl.execCall('umount {mp}/dev'.format(mp = mountPoint))
-      sltl.execCall('umount {mp}/proc'.format(mp = mountPoint))
-      sltl.execCall('umount {mp}/sys'.format(mp = mountPoint))
+      slt.execCall('umount {mp}/dev'.format(mp = mountPoint))
+      slt.execCall('umount {mp}/proc'.format(mp = mountPoint))
+      slt.execCall('umount {mp}/sys'.format(mp = mountPoint))
 
   def _copyAndInstallGrub2(self, mountPoint, device):
     if self.isTest:
       self.__debug("/usr/sbin/grub-install --boot-directory {bootdir} --no-floppy {dev}".format(bootdir = os.path.join(mountPoint, "boot"), dev = device))
       return True
     else:
-      return sltl.execCall("/usr/sbin/grub-install --boot-directory {bootdir} --no-floppy {dev}".format(bootdir = os.path.join(mountPoint, "boot"), dev = device))
+      return slt.execCall("/usr/sbin/grub-install --boot-directory {bootdir} --no-floppy {dev}".format(bootdir = os.path.join(mountPoint, "boot"), dev = device))
 
   def _installGrub2Config(self, mountPoint):
     if os.path.exists(os.path.join(mountPoint, 'etc/default/grub')) and os.path.exists(os.path.join(mountPoint, 'usr/sbin/update-grub')):
@@ -107,14 +107,14 @@ class Grub2:
       if self.isTest:
         self.__debug("chroot {mp} /usr/sbin/update-grub".format(mp = mountPoint))
       else:
-        sltl.execCall("chroot {mp} /usr/sbin/update-grub".format(mp = mountPoint))
+        slt.execCall("chroot {mp} /usr/sbin/update-grub".format(mp = mountPoint))
     else:
       self.__debug("grub2 not installed on the target partition, so grub_mkconfig will directly be used to generate the grub.cfg file")
       # tiny OS installed on that mount point, so we cannot chroot on it to install grub2 config.
       if self.isTest:
         self.__debug("/usr/sbin/grub-mkconfig -o {cfg}".format(cfg = os.path.join(mountPoint, "boot/grub/grub.cfg")))
       else:
-        sltl.execCall("/usr/sbin/grub-mkconfig -o {cfg}".format(cfg = os.path.join(mountpoint, "boot/grub/grub.cfg")))
+        slt.execCall("/usr/sbin/grub-mkconfig -o {cfg}".format(cfg = os.path.join(mountpoint, "boot/grub/grub.cfg")))
 
   def _umountAll(self, mountPoint):
     self.__debug("umountAll")
@@ -123,10 +123,10 @@ class Grub2:
       self._unbindProcSysDev(mountPoint)
       if self._bootInBootMounted:
         self.__debut("/boot mounted in " + mountPoint + ", so umount it")
-        sltl.execCall("chroot {mp} /sbin/umount /boot".format(mp = mountPoint))
+        slt.execCall("chroot {mp} /sbin/umount /boot".format(mp = mountPoint))
       if mountPoint != '/':
         self.__debug("umain mount point ≠ '/' → umount " + mountPoint)
-        sltl.umountDevice(mountPoint)
+        slt.umountDevice(mountPoint)
     self._bootInBootMounted = False
     self._procInBootMounted = False
 
index ab1db9c..7bc6e9f 100644 (file)
@@ -1,10 +1,10 @@
 #!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
+# coding: utf-8
+# vim:et:sta:sts=2:sw=2:ts=2:tw=0:
 """
 LiLo for BootSetup.
 """
-from __future__ import unicode_literals
+from __future__ import unicode_literals, print_function, division, absolute_import
 
 __copyright__ = 'Copyright 2013-2014, Salix OS'
 __license__ = 'GPL2+'
@@ -15,7 +15,7 @@ import shutil
 import os
 import glob
 import codecs
-import salix_livetools_library as sltl
+import libsalt as slt
 import subprocess
 from operator import itemgetter
 
@@ -94,7 +94,7 @@ vga = {vga}
     self.isTest = isTest
     self._prefix = "bootsetup.lilo-"
     self._tmp = tempfile.mkdtemp(prefix = self._prefix)
-    sltl.mounting._tempMountDir = os.path.join(self._tmp, 'mounts')
+    slt.mounting._tempMountDir = os.path.join(self._tmp, 'mounts')
     self.__debug("tmp dir = " + self._tmp)
 
   def __del__(self):
@@ -105,9 +105,9 @@ vga = {vga}
         if os.path.exists(cfgPath):
           self.__debug("Remove " + cfgPath)
           os.remove(cfgPath)
-        if os.path.exists(sltl.mounting._tempMountDir):
-          self.__debug("Remove " + sltl.mounting._tempMountDir)
-          os.rmdir(sltl.mounting._tempMountDir)
+        if os.path.exists(slt.mounting._tempMountDir):
+          self.__debug("Remove " + slt.mounting._tempMountDir)
+          os.rmdir(slt.mounting._tempMountDir)
         self.__debug("Remove " + self._tmp)
         os.rmdir(self._tmp)
       except:
@@ -115,7 +115,7 @@ vga = {vga}
 
   def __debug(self, msg):
     if self.isTest:
-      print "Debug: " + msg
+      print("Debug: " + msg)
       with codecs.open("bootsetup.log", "a+", "utf-8") as fdebug:
         fdebug.write("Debug: {0}\n".format(msg))
 
@@ -127,12 +127,12 @@ vga = {vga}
     Return the mount point
     """
     self.__debug("bootPartition = " + self._bootPartition)
-    if sltl.isMounted(self._bootPartition):
+    if slt.isMounted(self._bootPartition):
       self.__debug("bootPartition already mounted")
-      mp = sltl.getMountPoint(self._bootPartition)
+      mp = slt.getMountPoint(self._bootPartition)
     else:
       self.__debug("bootPartition not mounted")
-      mp = sltl.mountDevice(self._bootPartition)
+      mp = slt.mountDevice(self._bootPartition)
     if mp:
       self._mountBootInPartition(mp)
     return mp
@@ -145,9 +145,9 @@ vga = {vga}
       self.__debug("mp != / and etc/fstab + boot exists, will try to mount /boot by reading fstab")
       try:
         self.__debug('set -- $(grep /boot {fstab}) && echo "$1,$3"'.format(fstab = fstab))
-        (bootDev, bootType) = sltl.execGetOutput('set -- $(grep /boot {fstab}) && echo "$1,$3"'.format(fstab = fstab), shell = True)[0].split(',')
+        (bootDev, bootType) = slt.execGetOutput('set -- $(grep /boot {fstab}) && echo "$1,$3"'.format(fstab = fstab), shell = True)[0].split(',')
         if bootDev and not os.path.ismount(bootdir):
-          mp = sltl.mountDevice(bootDev, fsType = bootType, mountPoint = bootdir)
+          mp = slt.mountDevice(bootDev, fsType = bootType, mountPoint = bootdir)
           if mp:
             self._bootsMounted.append(mp)
             self.__debug("/boot mounted in " + mp)
@@ -164,10 +164,10 @@ vga = {vga}
       for p in partitionsToMount:
         dev = os.path.join("/dev", p[0])
         self.__debug("mount partition " + dev)
-        if sltl.isMounted(dev):
-          mp = sltl.getMountPoint(dev)
+        if slt.isMounted(dev):
+          mp = slt.getMountPoint(dev)
         else:
-          mp = sltl.mountDevice(dev)
+          mp = slt.mountDevice(dev)
         self.__debug("mount partition " + dev + " => " + unicode(mp))
         if mp:
           mountPointList[p[0]] = mp
@@ -180,7 +180,7 @@ vga = {vga}
     if mountPoint:
       for mp in self._bootsMounted:
         self.__debug("umounting " + unicode(mp))
-        sltl.umountDevice(mp, deleteMountPoint = False)
+        slt.umountDevice(mp, deleteMountPoint = False)
       self._bootsMounted = []
       if mountPointList:
         self.__debug("umount other mount points: " + unicode(mountPointList))
@@ -188,10 +188,10 @@ vga = {vga}
           if mp == mountPoint:
             continue # skip it, will be unmounted just next
           self.__debug("umount " + unicode(mp))
-          sltl.umountDevice(mp)
+          slt.umountDevice(mp)
       if mountPoint != '/':
         self.__debug("main mount point ≠ '/' → umount " + mountPoint)
-        sltl.umountDevice(mountPoint)
+        slt.umountDevice(mountPoint)
 
   def _createLiloSections(self, mountPointList):
     """
@@ -238,7 +238,7 @@ vga = {vga}
           l.remove(el)
     self.__debug("kernelList: " + unicode(kernelList))
     self.__debug("initrdList: " + unicode(initrdList))
-    uuid = sltl.execGetOutput(['/sbin/blkid', '-s', 'UUID', '-o', 'value', device], shell = False)
+    uuid = slt.execGetOutput(['/sbin/blkid', '-s', 'UUID', '-o', 'value', device], shell = False)
     if uuid:
       rootDevice = "/dev/disk/by-uuid/{uuid}".format(uuid = uuid[0])
     else:
@@ -292,7 +292,7 @@ vga = {vga}
     Format: (fb, label)
     """
     try:
-      fbGeometry = sltl.execGetOutput("/usr/sbin/fbset | grep -w geometry")
+      fbGeometry = slt.execGetOutput("/usr/sbin/fbset | grep -w geometry")
     except subprocess.CalledProcessorError:
       self.__debug("Impossible to determine frame buffer mode, default to text.")
       fbGeometry = None
@@ -471,8 +471,8 @@ vga = {vga}
         # run lilo
         if self.isTest:
           self.__debug('/sbin/lilo -t -v -C {mp}/etc/bootsetup/lilo.conf'.format(mp = mp))
-          sltl.execCall('/sbin/lilo -t -v -C {mp}/etc/bootsetup/lilo.conf'.format(mp = mp))
+          slt.execCall('/sbin/lilo -t -v -C {mp}/etc/bootsetup/lilo.conf'.format(mp = mp))
         else:
-          sltl.execCall('/sbin/lilo -C {mp}/etc/bootsetup/lilo.conf'.format(mp = mp))
+          slt.execCall('/sbin/lilo -C {mp}/etc/bootsetup/lilo.conf'.format(mp = mp))
       finally:
         self._umountAll(mp, mpList)
diff --git a/src/lib/salix_livetools_library/__init__.py b/src/lib/salix_livetools_library/__init__.py
deleted file mode 100644 (file)
index 1f85d8b..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Salix Live Installer library used by both the GUI and the Ncurses installers.
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-from bootloader import *
-from chroot import *
-from disk import *
-from execute import *
-from freesize import *
-from fs import *
-from fstab import *
-from kernel import *
-from keyboard import *
-from language import *
-from mounting import *
-from salt import *
-from timezone import *
-from user import *
diff --git a/src/lib/salix_livetools_library/assertPlus.py b/src/lib/salix_livetools_library/assertPlus.py
deleted file mode 100644 (file)
index c62efac..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-assert functions:
- - assertTrue(expression)
- - assertFalse(expression)
- - assertEquals(expected, expression)
- - assertNotEquals(expected, expression)
- - assertException(exceptionType, function)
- - assertNoException(function)
-
-To pass a function, you can use lambda expression like:
-  assertException(lambda: myfunction())
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-def assertTrue(expression):
-  """Expect the expression to be true"""
-  assert expression, "'{0}' was expected to be true".format(expression)
-def assertFalse(expression):
-  """Expect the expression to be false"""
-  assert (not expression), "'{0}' was expected to be false".format(expression)
-def assertEquals(expected, expression):
-  """Expect the expression to be equal to the expected value"""
-  assert expression == expected, "'{0}' expected, got '{1}'".format(expected, expression)
-def assertNotEquals(expected, expression):
-  """Expect the expression not to be equal to the expected value"""
-  assert expression != expected, "'{0}' not expected, got '{1}'".format(expected, expression)
-def assertException(exceptionType, function):
-  """Expect the function to trigger an exception with exceptionType type"""
-  triggered = False
-  try:
-    function()
-  except BaseException as e:
-    if isinstance(e, exceptionType):
-      triggered = True
-  assert triggered, "Exception '{0}' expected with '{1}'".format(exceptionType, function.__doc__)
-def assertNoException(function):
-  """Expect the function not to trigger any exception"""
-  triggered = False
-  unExpectedE = None
-  try:
-    function()
-  except BaseException as e:
-    unExpectedE = e
-    triggered = True
-  assert (not triggered), "Exception '{0}' was not expected with '{1}'".format(unExpected, function.__doc__)
-
-# Unit test
-if __name__ == '__main__':
-  assertTrue(True)
-  assertFalse(False)
-  assertEquals(0, 2 - 2)
-  assertNotEquals(1, 2 - 2)
-  assertException(ZeroDivisionError, lambda: 1 / 0)
-  assertNoException(lambda: 1)
diff --git a/src/lib/salix_livetools_library/bootloader.py b/src/lib/salix_livetools_library/bootloader.py
deleted file mode 100644 (file)
index 5096a49..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Function to launch the boot loader setup tool with some defaults:
-  - isBootsetupAvailable
-  - runBootsetup
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-from execute import *
-
-def isBootsetupAvailable():
-  try:
-    execGetOutput(['bootsetup', '--version'], withError = True)
-    return True
-  except:
-    return False
-
-def runBootsetup(bootloader = 'lilo'):
-  if isBootsetupAvailable():
-    try:
-      execCheck(['bootsetup', bootloader], env = None)
-      return True
-    except:
-      return False
-  else:
-    try:
-      execCheck(['lilosetup.py'], env = None)
-      return True
-    except:
-      return False
-
-# Unit test
-if __name__ == '__main__':
-  from assertPlus import *
-  assertTrue(runBootsetup())
diff --git a/src/lib/salix_livetools_library/chroot.py b/src/lib/salix_livetools_library/chroot.py
deleted file mode 100644 (file)
index f3596ad..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Chroot function
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-from execute import *
-import os
-
-def execChroot(path, cmd, shell = False):
-  """
-  Execute cmd in the chroot defined by path.
-  Paths in cmd should be relative to the new root directory.
-  """
-  checkRoot()
-  if not path:
-    raise IOError("You should provide a path to change the root directory.")
-  elif not os.path.isdir(path):
-    raise IOError("'{0}' does not exist or is not a directory.".format(path))
-  chrootCmd = ['chroot', path]
-  if shell:
-    chrootCmd.append('sh')
-    chrootCmd.append('-c')
-    if type(cmd) == list:
-      chrootCmd.append(' '.join(cmd))
-    else:
-      chrootCmd.append(cmd)
-  else:
-    if type(cmd) == list:
-      chrootCmd.extend(cmd)
-    else:
-      chrootCmd.append(cmd)
-  return execCall(chrootCmd, shell = False)
-
-# Unit test
-if __name__ == '__main__':
-  from assertPlus import *
-  assertException(IOError, lambda: execChroot(None, '/bin/ls'))
-  assertException(IOError, lambda: execChroot('/nonExistant', '/bin/ls'))
-  assertEquals(0, execChroot('/', '/bin/ls'))
-  assertEquals(0, execChroot('/', "/bin/ls | grep '.' && echo '** chroot ok **'", shell = True))
diff --git a/src/lib/salix_livetools_library/disk.py b/src/lib/salix_livetools_library/disk.py
deleted file mode 100644 (file)
index 64a94bb..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Get information from the system disks and partitions.
-For now it only handles (S/P)ATA disks and partitions, RAID and LVM are not supported yet.
-/proc and /sys should be mounted to retrieve information.
-Functions:
-  - getDisks
-  - getDiskInfo
-  - getPartitions
-  - getSwapPartitions
-  - getPartitionInfo
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-from execute import *
-from fs import *
-from freesize import *
-import glob
-import re
-import os
-from stat import *
-
-def getDisks():
-  """
-  Returns the disks devices (without /dev/) connected to the computer. 
-  RAID and LVM are not supported yet.
-  """
-  ret = []
-  for l in open('/proc/partitions', 'r').read().splitlines():
-    if re.search(r' sd[^0-9]+$', l):
-      ret.append(re.sub(r'.*(sd.*)', r'\1', l))
-  return ret
-
-def getDiskInfo(diskDevice):
-  """
-  Returns a dictionary with the following disk device's info:
-    - model: model name
-    - size: size in bytes
-    - sizeHuman: human readable size
-    - removable: whether it is removable or not
-  diskDevice should no be prefixed with '/dev/'
-  """
-  if S_ISBLK(os.stat('/dev/{0}'.format(diskDevice)).st_mode):
-    modelName = open('/sys/block/{0}/device/model'.format(diskDevice), 'r').read().strip()
-    blockSize = int(open('/sys/block/{0}/queue/logical_block_size'.format(diskDevice), 'r').read().strip())
-    size = int(open('/sys/block/{0}/size'.format(diskDevice), 'r').read().strip()) * blockSize
-    sizeHuman = getHumanSize(size)
-    try:
-      removable = int(open('/sys/block/{0}/removable'.format(diskDevice), 'r').read().strip()) == 1
-    except:
-      removable = False
-    return {'model':modelName, 'size':size, 'sizeHuman':sizeHuman, 'removable':removable}
-  else:
-    return None
-
-def getPartitions(diskDevice, skipExtended = True, skipSwap = True):
-  """
-  Returns partitions matching exclusion filters.
-  """
-  if S_ISBLK(os.stat('/dev/{0}'.format(diskDevice)).st_mode):
-    parts = [p.replace('/sys/block/{0}/'.format(diskDevice), '') for p in glob.glob('/sys/block/{0}/{0}*'.format(diskDevice))]
-    fsexclude = [False]
-    if skipExtended:
-      fsexclude.append('Extended')
-    if skipSwap:
-      fsexclude.append('swap')
-    return [part for part in parts if getFsType(part) not in fsexclude]
-  else:
-    return None
-
-def getSwapPartitions():
-  """
-  Returns partition devices with Linux Swap type.
-  """
-  ret = []
-  for diskDevice in getDisks():
-    parts = [p.replace('/sys/block/{0}/'.format(diskDevice), '') for p in glob.glob('/sys/block/{0}/{0}*'.format(diskDevice))]
-    ret.extend([part for part in parts if getFsType(part) == 'swap'])
-  return ret
-
-def getPartitionInfo(partitionDevice):
-  """
-  Returns a dictionary with the partition information:
-    - fstype
-    - label
-    - size
-    - sizeHuman
-  """
-  checkRoot()
-  if S_ISBLK(os.stat('/dev/{0}'.format(partitionDevice)).st_mode):
-    fstype = getFsType(partitionDevice)
-    label = getFsLabel(partitionDevice)
-    diskDevice = re.sub('[0-9]*', '', partitionDevice)
-    blockSize = int(open('/sys/block/{0}/queue/logical_block_size'.format(diskDevice), 'r').read().strip())
-    size = int(open('/sys/block/{0}/{1}/size'.format(diskDevice, partitionDevice), 'r').read().strip()) * blockSize
-    sizeHuman = getHumanSize(size)
-    return {'fstype':fstype, 'label':label, 'size':size, 'sizeHuman':sizeHuman}
-  else:
-    return None
-
-# Unit test
-if __name__ == '__main__':
-  from assertPlus import *
-  checkRoot()
-  disks = getDisks()
-  assertTrue(len(disks) > 0)
-  assertEquals('sda', disks[0])
-  diskInfo = getDiskInfo('sda')
-  assertTrue(diskInfo['model'] != '')
-  assertTrue(diskInfo['size'] > 0)
-  assertTrue('B' in diskInfo['sizeHuman'])
-  assertFalse(diskInfo['removable'])
-  assertTrue(len(getPartitions('sda')) > 0)
-  assertTrue(len(getSwapPartitions()) > 0)
-  partInfo = getPartitionInfo('sda1')
-  assertTrue(partInfo['fstype'] != '')
-  assertTrue(partInfo['label'] != '')
-  assertTrue(partInfo['size'] > 0)
-  assertTrue('B' in partInfo['sizeHuman'])
diff --git a/src/lib/salix_livetools_library/execute.py b/src/lib/salix_livetools_library/execute.py
deleted file mode 100644 (file)
index e3e34db..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Functions to execute native commands and get their output:
-  - execCall
-  - execCheck
-  - execGetOutput
-  - checkRoot
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-import subprocess
-import sys
-import os
-
-def execCall(cmd, shell = True, env = {'LANG' : 'en_US'}):
-  """
-  Executes a command and return the exit code.
-  The command is executed by default in a /bin/sh shell with en_US locale.
-  The output of the command is not read. With some commands, it may hang if the output is not read when run in a shell.
-  For this type of command, it is preferable to use execGetOutput even the return value is not read, or to use shell = False.
-  """
-  if shell and isinstance(cmd, list):
-    cmd = ' '.join(cmd)
-  return subprocess.call(cmd, shell = shell, env = env)
-
-def execCheck(cmd, shell = True, env = {'LANG' : 'en_US'}):
-  """
-  Executes a command and return 0 if Ok or a subprocess.CalledProcessorError exception in case of error.
-  The command is executed by default in a /bin/sh shell with en_US locale.
-  """
-  if shell and isinstance(cmd, list):
-    cmd = ' '.join(cmd)
-  return subprocess.check_call(cmd, shell = shell, env = env)
-
-def execGetOutput(cmd, withError = False, shell = True, env = {'LANG' : 'en_US'}):
-  """
-  Executes a command and return its output in a list, line by line.
-  In case of error, it returns a subprocess.CalledProcessorError exception.
-  The command is executed by default in a /bin/sh shell with en_US locale.
-  """
-  DEVNULL = open(os.devnull, 'wb')
-  stdErr = DEVNULL
-  if withError:
-    stdErr = subprocess.STDOUT
-  if sys.version_info[0] > 2 or (sys.version_info[0] == 2 and sys.version_info[1] >= 7): # ver >= 2.7
-    if shell and isinstance(cmd, list):
-      cmd = ' '.join(cmd)
-    return subprocess.check_output(cmd, stderr = stdErr, shell = shell, env = env).splitlines()
-  else:
-    wrappedCmd = []
-    if shell:
-      wrappedCmd.append('sh')
-      wrappedCmd.append('-c')
-      if isinstance(cmd, list):
-        wrappedCmd.append(' '.join(cmd))
-      else:
-        wrappedCmd.append(cmd)
-    else:
-      if isinstance(cmd, list):
-        wrappedCmd = cmd
-      else:
-        wrappedCmd.append(cmd)
-    p = subprocess.Popen(wrappedCmd, stdout = subprocess.PIPE, stderr = stdErr)
-    output = p.communicate()[0]
-    if p.returncode == 0:
-      return output.splitlines()
-    else:
-      raise subprocess.CalledProcessError(returncode = p.returncode, cmd = cmd)
-
-def checkRoot():
-  """
-  Raises an Exception if you run this code without root permissions
-  """
-  if os.getuid() != 0:
-    raise Exception('You need root permissions.')
-
-# Unit test
-if __name__ == '__main__':
-  from assertPlus import *
-  assertEquals(0, execCall("ls"))
-  assertEquals(0, execCall("ls -lh | grep '[.]'"))
-  assertEquals(0, execCall("ls", shell = False))
-  assertEquals(127, execCall("xyz"))
-  assertException(subprocess.CalledProcessError, lambda: execCheck("xyz"))
-  assertEquals(0, execCheck("ls"))
-  assertEquals(os.getcwd(), execGetOutput("pwd")[0].strip())
-  assertException(Exception, lambda: checkRoot())
diff --git a/src/lib/salix_livetools_library/freesize.py b/src/lib/salix_livetools_library/freesize.py
deleted file mode 100644 (file)
index be8d8d1..0000000
+++ /dev/null
@@ -1,174 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Calculate some size and free size of folders and mount points.
-Functions:
-  - getHumanSize
-  - getBlockSize
-  - getSizes
-  - getUsedSize
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-import re
-import os
-from stat import *
-from execute import *
-
-def _getMountPoint(device):
-  """
-  Finds the mount point of 'device' or None if not mounted
-  Copied from 'mounting' module to break circular dependencies
-  """
-  mountpoint = None
-  for line in execGetOutput('mount', shell = False):
-    p, _, mp, _ = line.split(' ', 3) # 3 splits max, _ is discarded
-    if os.path.islink(p):
-      p = os.path.realpath(p)
-    if p == device:
-      mountpoint = mp
-      break
-  return mountpoint
-
-def getHumanSize(size):
-  """
-  Returns the human readable format of the size in bytes
-  """
-  units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
-  unit = 0
-  sizeHuman = float(size)
-  while sizeHuman > 1024 and unit < len(units) - 1:
-    unit += 1
-    sizeHuman = sizeHuman / 1024
-  return "{0:.1f}{1}".format(sizeHuman, units[unit])
-
-def getBlockSize(path):
-  """
-  Returns the block size of the underlying filesystem denoted by 'path'.
-  """
-  if S_ISBLK(os.stat(path).st_mode):
-    lines = execGetOutput(['/sbin/blockdev', '--getbsz', path], shell = False)
-    if lines:
-      blockSize = int(lines[0])
-    else:
-      blockSize = None
-  else:
-    st = os.statvfs(path)
-    blockSize = st.f_frsize
-  return blockSize
-
-def getSizes(path, withHuman = True):
-  """
-  Computes the different sizes of the fileystem denoted by path (either a device or a file in filesystem).
-  Return the following sizes (in a dictionary):
-    - size (total size)
-    - free (total free size)
-    - uuFree (free size for unprivileged users)
-    - used (size - free)
-    - uuUsed (size - uuFree)
-  + all of them with the corresponding 'Human' suffix.
-  """
-  if S_ISBLK(os.stat(path).st_mode):
-    mountpoint = _getMountPoint(path)
-    if mountpoint:
-      # mounted, so will use mountpoint to get information about different sizes
-      path = mountpoint
-    else:
-      # not mounted, so only the full size could be get
-      diskDevice = re.sub(r'^.*/([^/]+?)[0-9]*$', r'\1', path)
-      device = re.sub(r'^.*/([^/]+)$', r'\1', path)
-      blockSize = int(open('/sys/block/{0}/queue/logical_block_size'.format(diskDevice), 'r').read().strip())
-      size = int(open('/sys/class/block/{0}/size'.format(device), 'r').read().strip()) * blockSize
-      return {
-          'size':size, 'sizeHuman':getHumanSize(size),
-          'free':None, 'freeHuman':None,
-          'uuFree':None, 'uuFreeHuman':None,
-          'used':None, 'usedHuman':None,
-          'uuUsed':None, 'uuUsedHuman':None
-        }
-  st = os.statvfs(path)
-  size = st.f_blocks * st.f_frsize
-  free = st.f_bfree * st.f_frsize
-  uuFree = st.f_bavail * st.f_frsize # free size for unpriviliedge users
-  used = size - free
-  uuUsed = size - uuFree # used size appear differently for commun users than from root user
-  if withHuman:
-    return {
-        'size':size, 'sizeHuman':getHumanSize(size),
-        'free':free, 'freeHuman':getHumanSize(free),
-        'uuFree':uuFree, 'uuFreeHuman':getHumanSize(uuFree),
-        'used':used, 'usedHuman':getHumanSize(used),
-        'uuUsed':uuUsed, 'uuUsedHuman':getHumanSize(uuUsed)
-      }
-  else:
-    return {
-        'size':size, 'sizeHuman':None,
-        'free':free, 'freeHuman':None,
-        'uuFree':uuFree, 'uuFreeHuman':None,
-        'used':used, 'usedHuman':None,
-        'uuUsed':uuUsed, 'uuUsedHuman':None
-      }
-
-def getUsedSize(path, blocksize = None, withHuman = True):
-  """
-  Returns the size of the space used by files and folders under 'path'.
-  If 'blocksize' is specified, mimic the space that will be used if the blocksize of the underlying filesystem where the one specified.
-  This could be useful if used to transfer files from one directory to another when the target filesystem use another blocksize.
-  Returns a tuple with (size, sizeHuman)
-  """
-  if blocksize:
-    cmd = """echo $((($(find '{0}' -type f -print0 | du -l -B {1} --files0-from=- | cut -f1 | tr "\\n" "+")0)*{1}))""".format(path, blocksize)
-    lines = execGetOutput(cmd, shell = True)
-    size = lines[-1]
-  else:
-    cmd = ['/bin/du', '-c', '-s', '-B', '1', path]
-    lines = execGetOutput(cmd, shell = False)
-    size, _ = lines[-1].split()
-  size = int(size)
-  if withHuman:
-    return {'size':size, 'sizeHuman':getHumanSize(size)}
-  else:
-    return {'size':size, 'sizeHuman':None}
-
-# Unit test
-if __name__ == '__main__':
-  from assertPlus import *
-  assertEquals('7.4GB', getHumanSize(7923593216L))
-  print '/'
-  stats = getSizes('/')
-  print stats
-  assertTrue(stats['size'] > 0)
-  assertTrue(stats['free'] > 0)
-  assertTrue(stats['uuFree'] > 0)
-  assertTrue(stats['used'] > 0)
-  assertTrue(stats['uuUsed'] > 0)
-  print '/dev/sda1'
-  stats = getSizes('/dev/sda1') # mounted
-  print stats
-  assertTrue(stats['size'] > 0)
-  assertTrue(stats['size'] > 0)
-  assertTrue(stats['free'] > 0)
-  assertTrue(stats['uuFree'] > 0)
-  assertTrue(stats['used'] > 0)
-  assertTrue(stats['uuUsed'] > 0)
-  print '/dev/sda2'
-  stats = getSizes('/dev/sda2') # extended partition, could never have been mounted
-  print stats
-  assertTrue(stats['size'] > 0)
-  assertTrue(stats['size'] > 0)
-  assertTrue(stats['free'] == None)
-  assertTrue(stats['uuFree'] == None)
-  assertTrue(stats['used'] == None)
-  assertTrue(stats['uuUsed'] == None)
-  print 'getUsedSize(.)'
-  stats1 = getUsedSize('.')
-  print stats1
-  assertTrue(stats1['size'] > 0)
-  print 'getUsedSize(., 524288)'
-  stats2 = getUsedSize('.', 524288)
-  print stats2
-  assertTrue(stats2['size'] > 0)
-  assertTrue(stats2['size'] > stats1['size'])
diff --git a/src/lib/salix_livetools_library/fs.py b/src/lib/salix_livetools_library/fs.py
deleted file mode 100644 (file)
index 57a0e0b..0000000
+++ /dev/null
@@ -1,275 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Get information about filesystem, create them, ...
-For now it only handles (S/P)ATA disks and partitions. RAID and LVM are not supported.
-/proc and /sys should be mounted for getting information
-Functions:
-  - getFsType
-  - getFsLabel
-  - makeFs
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-from execute import *
-from freesize import getSizes
-import os
-from stat import *
-import re
-
-def getFsType(partitionDevice):
-  """
-  Returns the file system type for that partition.
-  'partitionDevice' should no be prefixed with '/dev/' if it's a block device.
-  It can be a full path if the partition is contained in a file.
-  Returns 'Extended' if the partition is an extended partition and has no filesystem.
-  """
-  if os.path.exists('/dev/{0}'.format(partitionDevice)) and S_ISBLK(os.stat('/dev/{0}'.format(partitionDevice)).st_mode):
-    path = '/dev/{0}'.format(partitionDevice)
-  elif os.path.isfile(partitionDevice):
-    path = partitionDevice
-  else:
-    fstype = False
-    path = False
-  if path:
-    try:
-      fstype = execGetOutput(['/sbin/blkid', '-s', 'TYPE', '-o', 'value', path], shell = False)
-      if fstype:
-        fstype = fstype[0]
-      else:
-        fstype = False
-    except subprocess.CalledProcessError as e:
-      fstype = False
-    if not fstype:
-      # is it a real error or is it an extended partition?
-      try:
-        filetype = execGetOutput(['/usr/bin/file', '-s', path], shell = False)
-        if 'extended partition table' in filetype:
-          fstype = 'Extended'
-      except subprocess.CalledProcessError:
-        pass
-  return fstype
-
-def getFsLabel(partitionDevice):
-  """
-  Returns the label for that partition (if any).
-  'partitionDevice' should no be prefixed with '/dev/' if it is a block device.
-  It can be a full path if the partition is contained in a file.
-  """
-  if os.path.exists('/dev/{0}'.format(partitionDevice)) and S_ISBLK(os.stat('/dev/{0}'.format(partitionDevice)).st_mode):
-    path = '/dev/{0}'.format(partitionDevice)
-  elif os.path.isfile(partitionDevice):
-    path = partitionDevice
-  else:
-    label = False
-    path = False
-  if path:
-    try:
-      label = execGetOutput(['/sbin/blkid', '-s', 'LABEL', '-o', 'value', path], shell = False)
-      if label:
-        label = label[0]
-      else:
-        label = ''
-    except subprocess.CalledProcessError as e:
-      label = False
-  return label
-
-def makeFs(partitionDevice, fsType, label=None, force=False, options=None):
-  """
-  Creates a filesystem on the device.
-  'partitionDevice' should no be prefixed with '/dev/' if it is a block device.
-  'fsType' could be ext2, ext3, ext4, xfs, reiserfs, jfs, btrfs, ntfs, fat16, fat32, swap
-  Use 'force=True' if you want to force the creation of the filesystem and if 'partitionDevice' is a full path to a file (not a block device).
-  Use 'options' to force these options on the creation process (use a list)
-  """
-  if force and os.path.exists(partitionDevice):
-    path = partitionDevice
-  else:
-    path = '/dev/{0}'.format(partitionDevice)
-    if not os.path.exists(path):
-      raise IOError('{0} does not exist'.format(path))
-    if not S_ISBLK(os.stat(path).st_mode):
-      raise IOError('{0} is not a block device'.format(path))
-  if fsType not in ('ext2', 'ext3', 'ext4', 'xfs', 'reiserfs', 'jfs', 'btrfs', 'ntfs', 'fat16', 'fat32', 'swap'):
-    raise Exception('{0} is not a recognized filesystem.'.format(fsType))
-  if fsType in ('ext2', 'ext3', 'ext4'):
-    return _makeExtFs(path, int(fsType[3]), label, options, force)
-  elif fsType == 'xfs':
-    return _makeXfs(path, label, options, force)
-  elif fsType == 'reiserfs':
-    return _makeReiserfs(path, label, options, force)
-  elif fsType == 'jfs':
-    return _makeJfs(path, label, options, force)
-  elif fsType == 'btrfs':
-    return _makeBtrfs(path, label, options, force)
-  elif fsType == 'ntfs':
-    return _makeNtfs(path, label, options, force)
-  elif fsType in ('fat16', 'fat32'):
-    return _makeFat(path, fsType == 'fat32', label, options, force)
-  elif fsType == 'swap':
-    return _makeSwap(path, label, options, force)
-  return None # should not append
-
-def _makeExtFs(path, version, label, options, force):
-  """
-  ExtX block size: 4k per default in /etc/mke2fs.conf
-  """
-  cmd = ['/sbin/mkfs.ext{0:d}'.format(version)]
-  if not options:
-    options = []
-  if label:
-    if len(label) > 16: # max 16 bytes
-      label = label[0:15]
-    options.append('-L')
-    options.append(label)
-  if force:
-    options.append('-F')
-  cmd.extend(options)
-  cmd.append(path)
-  return execCall(cmd, shell = False)
-
-def _makeXfs(path, label, options, force):
-  """
-  http://blog.peacon.co.uk/wiki/Creating_and_Tuning_XFS_Partitions
-  """
-  cmd = ['/sbin/mkfs.xfs']
-  if not options:
-    options = ['-f'] # -f is neccessary to have this or you cannot create XFS on a non-empty partition or disk
-    if os.path.isfile(path):
-      size = os.stat(path).st_size
-    else:
-      size = getSizes(path)['size']
-    if size > 104857600: # > 100M
-      options.extend(['-l', 'size=64m,lazy-count=1']) # optimizations
-  if label:
-    if len(label) > 12: # max 12 chars
-      label = label[0:11]
-    options.append('-L')
-    options.append(label)
-  cmd.extend(options)
-  cmd.append(path)
-  print 'cmd={0}'.format(cmd)
-  return execCall(cmd, shell = False)
-
-def _makeReiserfs(path, label, options, force):
-  cmd = ['/sbin/mkfs.reiserfs']
-  if not options:
-    options = []
-  if label:
-    if len(label) > 16: # max 16 chars
-      label = label[0:15]
-    options.append('-l')
-    options.append(label)
-  if force:
-    options.append('-f')
-    options.append('-f') # twice for no confirmation
-  cmd.extend(options)
-  cmd.append(path)
-  return execCall(cmd, shell = False)
-
-def _makeJfs(path, label, options, force):
-  cmd = ['/sbin/mkfs.jfs']
-  if not options:
-    options = ['-f'] # if not specified, will ask to continue
-  if label:
-    if len(label) > 16: # max 16 chars
-      label = label[0:15]
-    options.append('-L')
-    options.append(label)
-  if force:
-    pass # no need to do anything
-  cmd.extend(options)
-  cmd.append(path)
-  return execCall(cmd, shell = False)
-
-def _makeBtrfs(path, label, options, force):
-  cmd = ['/sbin/mkfs.btrfs']
-  if not options:
-    options = []
-  if label:
-    options.append('-L')
-    options.append(label) # no restriction on size
-  if force:
-    pass # no need to do anything
-  cmd.extend(options)
-  cmd.append(path)
-  return execCall(cmd, shell = False)
-
-def _makeNtfs(path, label, options, force):
-  cmd = ['/sbin/mkfs.ntfs']
-  if not options:
-    options = ['-Q']
-  if label:
-    if len(label) > 32: # 32 chars max
-      label = label[0:31]
-    options.append('-L')
-    options.append(label)
-  if force:
-    options.append('-F')
-  cmd.extend(options)
-  cmd.append(path)
-  return execCall(cmd, shell = False)
-
-def _makeFat(path, is32, label, options, force):
-  cmd = ['/sbin/mkfs.vfat']
-  if is32:
-    size = ['-F', '32']
-  else:
-    size = ['-F', '16']
-  if not options:
-    options = size
-  else:
-    options.extend(size)
-  if label:
-    if len(label) > 11: # 8+3 bytes max
-      label = label[0:10]
-    options.append('-n')
-    options.append(label)
-  if force:
-    options.append('-I') # permit to use whole disk
-  cmd.extend(options)
-  cmd.append(path)
-  return execCall(cmd, shell = False)
-
-def _makeSwap(path, label, options, force):
-  cmd = ['/sbin/mkswap']
-  if not options:
-    options = ['-f'] # it is neccessary to have this or you cannot create a swap on a non-empty partition or disk
-  if label:
-    options.append('-L') # I didn't find any restriction in the label size
-    options.append(label)
-  if force:
-    pass # nothing to do, writing to a file is always ok
-  cmd.extend(options)
-  cmd.append(path)
-  return execCall(cmd, shell = False)
-
-# Unit test
-if __name__ == '__main__':
-  from assertPlus import *
-  part = 'sda1'
-  fstype = getFsType(part)
-  label = getFsLabel(part)
-  print '{0}: {1} ({2})'.format(part, fstype, label)
-  assertTrue(fstype)
-  assertTrue(len(fstype) > 0)
-  assertTrue(label)
-  assertTrue(len(label) > 0)
-  for ft in ('ext2', 'ext4', 'xfs', 'reiserfs', 'jfs', 'btrfs', 'ntfs', 'fat16', 'fat32', 'swap'):
-    f = '{0}.fs'.format(ft)
-    if ft == 'btrfs':
-      size = 300 # btrfs minimum size is 256M
-    else:
-      size = 50
-    execCall(['dd', 'if=/dev/zero', 'of={0}'.format(f), 'bs=1M', 'count={0:d}'.format(size)], shell = False)
-    assertEquals(0, makeFs(f, ft, 'test_{0}'.format(ft), True))
-    if ft in ('fat16', 'fat32'):
-      expectedFt = 'vfat'
-    else:
-      expectedFt = ft
-    assertEquals(expectedFt, getFsType(f))
-    assertEquals('test_{0}'.format(ft), getFsLabel(f))
-    os.unlink(f)
diff --git a/src/lib/salix_livetools_library/fstab.py b/src/lib/salix_livetools_library/fstab.py
deleted file mode 100644 (file)
index 00df32d..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Functions to generate fstab entries:
-  - createFsTab
-  - addFsTabEntry
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-from execute import *
-import os
-
-def createFsTab(fstabMountPoint):
-  """
-  Generates an empty /etc/fstab file
-  """
-  try:
-    os.mkdir('{0}/etc'.format(fstabMountPoint))
-  except:
-    pass
-  open('{0}/etc/fstab'.format(fstabMountPoint), 'w').close()
-
-def addFsTabEntry(fstabMountPoint, device, mountPoint, fsType = None, options = None, dumpFlag = 0, fsckOrder = 0):
-  """
-  Add a line to /etc/fstab
-  If fsType is None, then it will be guessed from the device by using blkid
-  If options is None, then it will be guessed from the fsType like this:
-    - 'proc', 'sysfs', 'devpts', 'tmpfs', 'swap' |=> 'defaults'
-    - 'ext2', 'ext3', 'ext4', 'xfs', 'reiserfs', 'btrfs', 'jfs' |=> 'defaults,noatime'
-    - 'ntfs' |=> 'umask=000'
-    - 'vfat' |=> 'defaults,utf8,umask=0,shortname=mixed'
-  """
-  if not fsType:
-    lines = execGetOutput(['/sbin/blkid', '-c', '/dev/null', '-s', 'TYPE', '-o', 'value', device])
-    if len(lines) > 0:
-      fsType = lines[0]
-    else:
-      raise IOError('Cannot determine the filesystem of {0}'.format(device))
-  if not options:
-    defaultOptions = {
-        'def':'defaults',
-        'linux':'defaults,noatime',
-        'ntfs':'umask=000',
-        'fat':'defaults,utf8,umask=0,shortname=mixed'
-      }
-    defaultOptionsPerFs = {
-        'proc':'def', 'sysfs':'def', 'devpts':'def', 'tmpfs':'def', 'swap':'def',
-        'ext2':'linux', 'ext3':'linux', 'ext4':'linux', 'xfs':'linux', 'reiserfs':'linux', 'btrfs':'linux', 'jfs':'linux',
-        'ntfs':'ntfs',
-        'vfat':'fat'
-      }
-    if fsType in defaultOptionsPerFs:
-      options = defaultOptions[defaultOptionsPerFs[fsType]]
-    else:
-      options = defaultOptions['def']
-  if fsType == 'ntfs':
-    fsType = 'ntfs-3g'
-  fp = open('{0}/etc/fstab'.format(fstabMountPoint), 'a')
-  fp.write('{device:20}{mountPoint:20}{fsType:15}{options:20}{dumpFlag:10}{fsckOrder:2}\n'.format(device = device, mountPoint = mountPoint, fsType = fsType, options = options, dumpFlag = dumpFlag, fsckOrder = fsckOrder))
-  fp.close()
-
-# Unit test
-if __name__ == '__main__':
-  from assertPlus import *
-  checkRoot()
-  if os.path.exists('./etc/fstab'):
-    try:
-      os.unlink('./etc/fstab')
-      os.rmdir('./etc')
-    except:
-      pass
-  createFsTab('.')
-  assertTrue(os.path.exists('./etc/fstab'))
-  addFsTabEntry('.', '/dev/sda1', '/', 'ext4', 'defaults', 1, 1)
-  addFsTabEntry('.', '/dev/sda1', '/root', None, None, 1, 2)
-  lines = open('./etc/fstab', 'r').read().splitlines()
-  assertEquals(2, len(lines))
-  line1, line2 = lines
-  dev, mp, fs, opts, dump, fsck = line1.split()
-  assertEquals('/dev/sda1', dev)
-  assertEquals('/', mp)
-  assertEquals('ext4', fs)
-  assertEquals('defaults', opts)
-  assertEquals('1', dump)
-  assertEquals('1', fsck)
-  dev, mp, fs, opts, dump, fsck = line2.split()
-  assertEquals('/dev/sda1', dev)
-  assertEquals('/root', mp)
-  assertTrue(len(fs) > 0)
-  assertTrue(len(opts) > 0)
-  assertEquals('1', dump)
-  assertEquals('2', fsck)
-  try:
-    os.unlink('./etc/fstab')
-    os.rmdir('./etc')
-  except:
-    pass
diff --git a/src/lib/salix_livetools_library/kernel.py b/src/lib/salix_livetools_library/kernel.py
deleted file mode 100644 (file)
index 9ae92db..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Functions to retrieve kernel parameters:
-  - hasKernelParam
-  - getKernelParamValue
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-import os
-
-def hasKernelParam(param):
-  """
-  Defines if the kernel parameter param has been defined on the kernel command line or not
-  """
-  if os.path.exists('/proc/cmdline'):
-    cmdline = open('/proc/cmdline', 'r').read().split()
-    for chunk in cmdline:
-      if param == chunk.split('=', 1)[0]:
-        return True
-  return False
-
-def getKernelParamValue(param):
-  """
-  Returns the value of the kernel parameter, None if this param has no value and False if this param does not exist.
-  """
-  if os.path.exists('/proc/cmdline'):
-    cmdline = open('/proc/cmdline', 'r').read().split()
-    for chunk in cmdline:
-      paramMap = chunk.split('=', 1)
-      if param == paramMap[0]:
-        if len(paramMap) > 1:
-          return paramMap[1]
-        else:
-          return None
-  return False
-
-# Unit test
-if __name__ == '__main__':
-  from assertPlus import *
-  # it is supposed that the /proc/cmdline always have "ro" and "root=XXX" parameters.
-  assertTrue(hasKernelParam('ro'))
-  assertTrue(hasKernelParam('root'))
-  assertFalse(hasKernelParam('nonexistant'))
-  assertNotEquals('', getKernelParamValue('root'))
-  assertEquals(None, getKernelParamValue('ro'))
-  assertEquals(False, getKernelParamValue('nonexistant'))
diff --git a/src/lib/salix_livetools_library/keyboard.py b/src/lib/salix_livetools_library/keyboard.py
deleted file mode 100644 (file)
index 1bd848d..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Functions to handle keyboard layout:
-  - findCurrentKeymap
-  - listAvailableKeymaps
-  - isNumLockEnabledByDefault
-  - isIbusEnabledByDefault
-  - setDefaultKeymap
-  - setNumLockDefault
-  - setIbusDefault
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-import os
-import re
-import glob
-from kernel import *
-from chroot import *
-from execute import checkRoot
-
-_keymapsLocation = ['/usr/share/salixtools/keymaps', '/mnt/salt/lib/keymaps', 'keymaps']
-
-def findCurrentKeymap(mountPoint = None):
-  """
-  Find the currently used console keymaps (as loaded by 'loadkeys') by looking in:
-    - /etc/rc.d/rc.keymap, or
-    - in the 'keyb=' kernel parameter
-  The detected keymap is then checked against the first column of one of the files: {0}
-  Returns None if not found
-  """.format(' '.join(_keymapsLocation))
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  kmFile = None
-  keymap = None
-  for kml in _keymapsLocation:
-    if mountPoint:
-      kml = mountPoint + kml
-    if os.path.isfile(kml):
-      kmFile = kml
-      break
-  if kmFile:
-    # first, try parsing /etc/rc.d/rc.keymap
-    try:
-      for line in open('{0}/etc/rc.d/rc.keymap'.format(mountPoint), 'rb').read().decode('utf8').splitlines():
-        if '.map' in line:
-          keymap = re.sub(r'^.* ([^ ]+)\.map$', r'\1', line)
-          break
-    except:
-      pass
-    if not keymap:
-      # second, try to read from the kernel parmeters
-      keybParam = getKernelParamValue('keyb')
-      if keybParam:
-        keymap = keybParam
-  if keymap:
-    # verify that the detected keymap actually exists
-    if not keymap in [line.split('|', 1)[0] for line in open(kmFile, 'r').read().splitlines() if line and line[0] != '#']:
-      keymap = None
-  return keymap
-
-def listAvailableKeymaps(mountPoint = None):
-  """
-  Returns a list of couple (keymap, keyboardType).
-  'keymap' is a Console keymap as found in /usr/share/kbd/
-  'keyboardType' is either 'azerty', 'qwerty', 'qwertz', etc and is there only for information
-  The keymaps are extracted from one of the files: {0}
-  """.format(' '.join(_keymapsLocation))
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  kmFile = None
-  keymaps = []
-  for kml in _keymapsLocation:
-    if mountPoint:
-      kml = mountPoint + kml
-    if os.path.isfile(kml):
-      kmFile = kml
-      break
-  if kmFile:
-    for keymap in sorted([line.split('|', 1)[0] for line in open(kmFile, 'r').read().splitlines() if line and line[0] != '#']):
-      keyboardType = '-'
-      typePosition = 6 # usr/share/kbd/keymaps/i386/azerty => 6
-      if mountPoint:
-        typePosition += len(mountPoint.spli('/') - 1)
-      kmPath = glob.glob('{0}/usr/share/kbd/keymaps/*/*/{1}.map.gz'.format(mountPoint, keymap))
-      if kmPath:
-        keyboardType = kmPath[0].split('/')[typePosition]
-        if keyboardType == 'all':
-          # then use the machine type ('mac' for example)
-          keyboardType = kmPath[0].split('/')[typePosition - 1]
-      keymaps.append((keymap, keyboardType))
-  return keymaps
-
-def isNumLockEnabledByDefault(mountPoint = None):
-  """
-  Returns True if the num lock is enabled by default.
-  To do this, the execute bit of /etc/rc.d/rc.numlock is checked.
-  """
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  return os.access('{0}/etc/rc.d/rc.numlock'.format(mountPoint), os.X_OK)
-
-def isIbusEnabledByDefault(mountPoint = None):
-  """
-  Returns True if the IBus is enabled by default.
-  To do this, the execute bit of /usr/bin/ibus-daemon and /etc/profile.d/ibus.sh are checked.
-  """
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  return os.access('{0}/usr/bin/ibus-daemon'.format(mountPoint), os.X_OK) and os.access('{0}/etc/profile.d/ibus.sh'.format(mountPoint), os.X_OK)
-
-def setDefaultKeymap(keymap, mountPoint = None):
-  """
-  Fix the configuration in /etc/rc.d/rc.keymap to use the specified 'keymap'.
-  This uses 'keyboardsetup' Salix tool.
-  """
-  checkRoot()
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = '/'
-  ret = execChroot(mountPoint, ['/usr/sbin/keyboardsetup', '-k', keymap, '-z'])
-  # This has been forgotten in keyboardsetup
-  os.chmod('{0}/etc/rc.d/rc.keymap'.format(mountPoint), 0755)
-  return ret
-
-def setNumLockDefault(enabled, mountPoint = None):
-  """
-  Fix the configuration for default numlock to be activated or not on boot.
-  This uses 'keyboardsetup' Salix tool.
-  """
-  checkRoot()
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = '/'
-  cmd = ['/usr/sbin/keyboardsetup', '-n']
-  if enabled:
-    cmd.append('on')
-  else:
-    cmd.append('off')
-  cmd.append('-z') # must be last option because of a bug in keyboardsetup
-  return execChroot(mountPoint, cmd)
-
-def setIbusDefault(enabled, mountPoint = None):
-  """
-  Fix the configuration for default Ibus activated on boot or not.
-  This uses 'keyboardsetup' Salix tool.
-  """
-  checkRoot()
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = '/'
-  cmd = ['/usr/sbin/keyboardsetup', '-i']
-  if enabled:
-    cmd.append('on')
-  else:
-    cmd.append('off')
-  cmd.append('-z') # must be last option because of a bug in keyboardsetup
-  return execChroot(mountPoint, cmd)
-
-# Unit test
-if __name__ == '__main__':
-  from assertPlus import *
-  checkRoot()
-  keymaps = listAvailableKeymaps()
-  assertTrue(type(keymaps) == list)
-  assertTrue(len(keymaps) > 0)
-  keymaps = dict(keymaps) # change it to dictionnary
-  assertEquals('azerty', keymaps['fr-latin9'])
-  keymap = findCurrentKeymap()
-  assertTrue(keymap)
-  numlock = isNumLockEnabledByDefault()
-  assertTrue(type(numlock) == bool)
-  ibus = isIbusEnabledByDefault()
-  assertTrue(type(ibus) == bool)
-  assertEquals(0, setDefaultKeymap('fr-latin1'))
-  assertEquals('fr-latin1', findCurrentKeymap())
-  assertEquals(0, setNumLockDefault(True))
-  assertTrue(isNumLockEnabledByDefault())
-  assertEquals(0, setIbusDefault(True))
-  assertTrue(isIbusEnabledByDefault())
-  # restore actual keyboard parameters
-  setDefaultKeymap(keymap)
-  setNumLockDefault(numlock)
-  setIbusDefault(ibus)
diff --git a/src/lib/salix_livetools_library/language.py b/src/lib/salix_livetools_library/language.py
deleted file mode 100644 (file)
index 3321ac3..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Functions to handle locales and languages:
-  - listAvailableLocales
-  - getCurrentLocale
-  - getDefaultLocale
-  - setDefaultLocale
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-import os
-import glob
-import locale
-import re
-import fileinput
-import sys
-from execute import *
-
-def listAvailableLocales(mountPoint = None):
-  """
-  Returns a list of couples (name, title) for available utf8 locales on the system under 'mountPoint'.
-  """
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  locales = []
-  libdir = 'lib'
-  if os.path.isdir('{0}/usr/lib64/locale'.format(mountPoint)):
-    libdir = 'lib64'
-  for path in sorted(glob.glob('{0}/usr/{1}/locale/*.utf8'.format(mountPoint, libdir))):
-    locale = os.path.basename(path).rsplit('.', 1)[0]
-    title = execGetOutput("strings {0}/LC_IDENTIFICATION | grep -i 'locale for'".format(path))[0]
-    locales.append((locale, title))
-  return locales
-
-def getCurrentLocale():
-  """
-  Returns the current used locale in the current environment.
-  """
-  lang, enc = locale.getdefaultlocale()
-  return "{0}.{1}".format(lang, re.sub(r'utf-8', r'utf8', enc.lower()))
-
-def getDefaultLocale(mountPoint = None):
-  """
-  Returns the default locale as defined in /etc/profile.d/lang.c?sh
-  """
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  locale = None
-  for f in ('lang.sh', 'lang.csh'):
-    if not locale:
-      for line in open('{0}/etc/profile.d/{1}'.format(mountPoint, f), 'rb').read().decode('utf8').splitlines():
-        if line.startswith('export LANG='):
-          locale = re.sub(r'export LANG=(.*)', r'\1', line)
-          break
-        elif line.startswith('setenv LANG '):
-          locale = re.sub(r'setenv LANG (.*)', r'\1', line)
-          break
-  return locale
-
-def setDefaultLocale(locale, mountPoint = None):
-  """
-  Set the default locale in the /etc/profile.d/lang.c?sh file
-  """
-  checkRoot()
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  fi = fileinput.FileInput('{0}/etc/profile.d/lang.sh'.format(mountPoint), inplace = 1)
-  for line in fi:
-    sys.stdout.write(re.sub(r'^(export LANG=).*', r'\1{0}'.format(locale), line))
-  fi.close()
-  fi = fileinput.FileInput('{0}/etc/profile.d/lang.csh'.format(mountPoint), inplace = 1)
-  for line in fi:
-    sys.stdout.write(re.sub(r'^(setenv LANG ).*', r'\1{0}'.format(locale), line))
-  fi.close()
-
-# Unit test
-if __name__ == '__main__':
-  from assertPlus import *
-  checkRoot()
-  locales = listAvailableLocales()
-  assertTrue(type(locales) == list)
-  assertTrue(len(locales) > 0)
-  assertTrue('fr_FR' in dict(locales))
-  locale = getCurrentLocale()
-  assertTrue(len(locale) > 3)
-  assertTrue('.utf8' in locale)
-  deflocale = getDefaultLocale()
-  assertTrue(len(deflocale) > 3)
-  assertTrue('.utf8' in deflocale)
-  setDefaultLocale('zu_ZA.utf8')
-  locale = getDefaultLocale()
-  assertEquals('zu_ZA.utf8', locale)
-  setDefaultLocale(deflocale)
diff --git a/src/lib/salix_livetools_library/mounting.py b/src/lib/salix_livetools_library/mounting.py
deleted file mode 100644 (file)
index e25289a..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Help mounting/unmounting a filesystem.
-Functions:
-  - getMountPoint
-  - isMounted
-  - mountDevice
-  - umountDevice
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-from execute import *
-from fs import getFsType
-import os
-from stat import *
-import re
-
-_tempMountDir = '/mnt/.tempSalt'
-
-def getTempMountDir():
-  return _tempMountDir
-
-def getMountPoint(device):
-  """
-  Find the mount point to this 'device' or None if not mounted.
-  """
-  mountpoint = None
-  path = os.path.abspath(device)
-  for line in execGetOutput(['/bin/mount'], shell = False):
-    p, _, mp, _ = line.split(' ', 3) # 3 splits max, _ is discarded
-    if os.path.islink(p):
-      p = os.path.realpath(p)
-    if p == path:
-      mountpoint = mp
-      break
-  return mountpoint
-
-def isMounted(device):
-  """
-  Same as os.path.ismount(path) but using a block device.
-  """
-  if getMountPoint(device):
-    return True
-  else:
-    return False
-
-def _deleteMountPoint(mountPoint):
-  # delete the empty directory
-  try:
-    os.rmdir(mountPoint)
-  except:
-    pass
-  # delete the temporary directory if not empty
-  if os.path.isdir(_tempMountDir):
-    try:
-      os.rmdir(_tempMountDir)
-    except:
-      pass
-
-def mountDevice(device, fsType = None, mountPoint = None):
-  """
-  Mount the 'device' of 'fsType' filesystem under 'mountPoint'.
-  If 'mountPoint' is not specified, '{0}/device' will be used.
-  Returns False if it fails or the mount point if it succeed.
-  """.format(_tempMountDir) 
-  if not fsType:
-    fsType = getFsType(re.sub(r'/dev/', '', device))
-  if not fsType:
-    return False
-  autoMP = False
-  if not mountPoint:
-    mountPoint = '{0}/{1}'.format(_tempMountDir, os.path.basename(device))
-    if os.path.exists(mountPoint):
-      return False
-    autoMP = True
-  if not os.path.exists(mountPoint):
-    try:
-      os.makedirs(mountPoint)
-    except os.error:
-      pass
-  ret = execCall(['mount', '-t', fsType, device, mountPoint], shell = False)
-  if ret != 0 and autoMP:
-    _deleteMountPoint(mountPoint)
-  else:
-    return mountPoint
-  return ret == 0
-
-def umountDevice(deviceOrPath, tryLazyUmount = True, deleteMountPoint = True):
-  """
-  Unmount the 'deviceOrPath' which could be a device or a mount point.
-  If umount failed, try again with a lazyUmount if 'tryLazyUmount' is True.
-  Will delete the mount point if 'deleteMountPoint' is True.
-  Returns False if it fails.
-  """
-  if S_ISBLK(os.stat(deviceOrPath).st_mode):
-    mountPoint = getMountPoint(deviceOrPath)
-  else:
-    mountPoint = deviceOrPath
-  if mountPoint:
-    ret = execCall(['umount', mountPoint], shell = False)
-    if ret != 0:
-       ret = execCall(['umount', '-l', mountPoint], shell = False)
-    if ret == 0 and deleteMountPoint:
-      _deleteMountPoint(mountPoint)
-    return ret == 0
-  else:
-    return False
-
-# Unit test
-if __name__ == '__main__':
-  from assertPlus import *
-  from fs import *
-  checkRoot() # need to be root to mount/umount
-  execCall(['dd', 'if=/dev/zero', 'of=ext4.fs', 'bs=1M', 'count=50'], shell = False)
-  makeFs('ext4.fs', 'ext4', 'test ext4', True)
-  assertFalse(isMounted('ext4.fs'))
-  assertEquals(0, mountDevice('ext4.fs'))
-  assertTrue(isMounted('ext4.fs'))
-  assertEquals('{0}/ext4.fs'.format(_tempMountDir), getMountPoint('ext4.fs'))
-  assertEquals(0, umountDevice('ext4.fs'))
-  assertFalse(isMounted('ext4.fs'))
-  os.unlink('ext4.fs')
diff --git a/src/lib/salix_livetools_library/salt.py b/src/lib/salix_livetools_library/salt.py
deleted file mode 100644 (file)
index 227529d..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-SaLT functions:
-  - getSaLTVersion
-  - isSaLTVersionAtLeast
-  - isSaLTLiveEnv
-  - isSaLTLiveCloneEnv
-  - getSaLTLiveMountPoint
-  - getSaLTRootDir
-  - getSaLTIdentFile
-  - getSaLTBaseDir
-  - listSaLTModules
-  - installSaLTModule
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-import os
-import glob
-import re
-import subprocess
-from freesize import *
-from threading import Thread
-from time import sleep
-
-def getSaLTVersion():
-  """
-  Returns the SaLT version if run in a SaLT Live environment
-  """
-  _checkLive()
-  return open('/mnt/salt/salt-version', 'r').read().strip()
-
-def isSaLTVersionAtLeast(version):
-  """
-  Returns True if the SaLT version is at least 'version'.
-  """
-  v = getSaLTVersion()
-  def vercmp(v1, v2):
-    def _makelist(v):
-      lst = [int(x) for x in re.sub(r'[a-z]', '', v.lower()).split('.')]
-      while lst[-1] == 0:
-        lst.pop()
-      return lst
-    return _makelist(v1).__ge__(_makelist(v2))
-  return vercmp(version, v)
-
-def isSaLTLiveEnv():
-  """
-  Returns True if it is executed in a SaLT Live environment, False otherwise
-  """
-  return os.path.isfile('/mnt/salt/salt-version') and os.path.isfile('/mnt/salt/tmp/distro_infos')
-
-def _checkLive():
-  if not isSaLTLiveEnv():
-    raise Exception('Not in SaLT Live environment.')
-
-def isSaLTLiveCloneEnv():
-  """
-  Returns True if it is executed in a SaLT LiveClone environment, False otherwise
-  """
-  if not isSaLTLiveEnv():
-    return False
-  else:
-    moduledir = '{0}/{1}/{2}/modules'.format(getSaLTLiveMountPoint(), getSaLTBaseDir(), getSaLTRootDir())
-    return os.path.isfile(moduledir + '/01-clone.salt')
-
-def getSaLTLiveMountPoint():
-  """
-  Returns the SaLT source mount point path. It could be the mount point of the optical drive or the USB stick for example.
-  """
-  _checkLive()
-  try:
-    # format:
-    # mountpoint:device
-    ret = open('/mnt/salt/tmp/distro_infos', 'r').read().splitlines()[0].split(':', 1)[0]
-  except:
-    ret = None
-  return "/mnt/salt{0}".format(ret)
-
-def getSaLTRootDir():
-  """
-  Returns the SaLT ROOT_DIR, which is the directory containing SaLT modules.
-  This is not the full path but a relative path to BASEDIR.
-  """
-  _checkLive()
-  ret = None
-  for line in open('/mnt/salt/etc/salt.cfg', 'r').read().splitlines():
-    if line.startswith('ROOT_DIR='):
-      ret = line.split('=', 1)[1]
-      break
-  return ret
-
-def getSaLTIdentFile():
-  """
-  Returns the SaLT IDENT_FILE, which is the file located at the root of a filesystem containing some SaLT information for this Live session.
-  This is not the full path but a relative path to the mount point.
-  """
-  _checkLive()
-  ret = None
-  for line in open('/mnt/salt/etc/salt.cfg', 'r').read().splitlines():
-    if line.startswith('IDENT_FILE='):
-      ret = line.split('=', 1)[1]
-      break
-  return ret
-
-def getSaLTBaseDir():
-  """
-  Returns the SaLT BASEDIR, which is the directory containing all files for this Live session.
-  This is not a full path but a relative path to the mount point.
-  """
-  _checkLive()
-  mountpoint = getSaLTLiveMountPoint()
-  identfile = getSaLTIdentFile()
-  ret = None
-  if mountpoint and identfile:
-    for line in open('{0}/{1}'.format(mountpoint, identfile), 'r').read().splitlines():
-      if line.startswith('basedir='):
-        ret = line.split('=', 1)[1]
-        break
-  if ret != None and len(ret) == 0:
-    ret = '.' # for not having empty path. GNU is ok having a path like a/b//c/d but it's preferable to have a/b/./c/d if possible
-  return ret
-
-def listSaLTModules():
-  """
-  Returns the list of SaLT modules for this Live session.
-  """
-  _checkLive()
-  moduledir = '{0}/{1}/{2}/modules'.format(getSaLTLiveMountPoint(), getSaLTBaseDir(), getSaLTRootDir())
-  return sorted(map(lambda(x): re.sub(r'.*/([^/]+).salt$', r'\1', x), glob.glob('{0}/*.salt'.format(moduledir))))
-
-def getSaLTModulePath(moduleName):
-  """
-  Get the module full path.
-  """
-  return '/mnt/salt/mnt/modules/{0}'.format(moduleName)
-
-def installSaLTModule(moduleName, moduleSize, targetMountPoint, callback, callback_args = (), interval = 10, completeCallback = None):
-  """
-  Install the module 'moduleName' from this Live session into the targetMountPoint.
-  'moduleSize' is the uncompressed size of the module expressed in bytes.
-  The 'callback' function will be called each 'interval' seconds with the pourcentage (0 ≤ x ≤ 1) of progression (based on used size of target partition) as first argument and all value of callback_args as next arguments
-  The 'completeCallback' function will be called after the completion of installation.
-  """
-  _checkLive()
-  src = getSaLTModulePath(moduleName)
-  if not os.path.isdir(src):
-    raise IOError("The module '{0}' does not exists".format(moduleName))
-  if not os.path.isdir(targetMountPoint):
-    raise IOError("The target mount point '{0}' does not exists".format(targetMountPoint))
-  def get_used_size(p):
-    return getSizes(p, False)['used']
-  class ExecCopyTask:
-    def _run(self, *args, **kwargs):
-      cmd = args[0]
-      self._p = subprocess.Popen(cmd)
-      self._p.wait()
-    def start(self, cmd):
-      self._t = Thread(target=self._run, args=(cmd,))
-      self._t.start()
-    def is_running(self):
-      return self._t and self._t.is_alive()
-    def stop(self):
-      if self._p:
-        self._p.kill()
-        self._p = None
-  init_size = get_used_size(targetMountPoint)
-  actual_size = init_size
-  t = ExecCopyTask()
-  t.start(['cp', '--preserve', '-r', '-f', '--remove-destination', '{0}/.'.format(src), targetMountPoint + '/'])
-  while t.is_running():
-    for x in range(interval):
-      sleep(1)
-      if not t.is_running():
-        break
-    if t.is_running():
-      actual_size = get_used_size(targetMountPoint)
-      diff_size = float(actual_size - init_size)
-      if diff_size < 0: # is this possible?
-        diff_size = 0
-      p = diff_size / moduleSize
-      if p > 1:
-        p = 1
-      if not callback(p, *callback_args):
-        t.stop()
-  if completeCallback:
-    completeCallback()
diff --git a/src/lib/salix_livetools_library/timezone.py b/src/lib/salix_livetools_library/timezone.py
deleted file mode 100644 (file)
index 8214ec6..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Functions to handle time zones:
-  - listTimeZones
-  - listTZContinents
-  - listTZCities
-  - getDefaultTimeZone
-  - setDefaultTimeZone
-  - isNTPEnabledByDefault
-  - setNTPDefault
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-import os
-import glob
-import re
-from shutil import copyfile
-from execute import checkRoot
-
-def listTimeZones(mountPoint = None):
-  """
-  Returns a dictionary of time zones, by continent.
-  """
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  tz = {}
-  for z in listTZContinents(mountPoint):
-    tz[z] = listTZCities(z, mountPoint)
-  return tz
-
-def listTZContinents(mountPoint = None):
-  """
-  Returns a sorted list of continents for time zones.
-  """
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  return sorted(map(os.path.basename, filter(lambda f: os.path.isdir(f), glob.glob('{0}/usr/share/zoneinfo/[A-Z]*'.format(mountPoint)))))
-
-def listTZCities(continent, mountPoint = None):
-  """
-  Returns a sorted list of cities for a specific continent's time zone.
-  """
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  if os.path.isdir('{0}/usr/share/zoneinfo/{1}'.format(mountPoint, continent)):
-    return sorted(map(os.path.basename, glob.glob('{0}/usr/share/zoneinfo/{1}/*'.format(mountPoint, continent))))
-  else:
-    return None
-
-def getDefaultTimeZone(mountPoint = None):
-  """
-  Returns the default time zone, by reading the /etc/localtime-copied-from symlink.
-  """
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  tz = None
-  if os.path.islink('{0}/etc/localtime-copied-from'.format(mountPoint)):
-    tz = re.sub(r'/usr/share/zoneinfo/', '', os.readlink('{0}/etc/localtime-copied-from'.format(mountPoint)))
-  return tz
-
-def setDefaultTimeZone(timezone, mountPoint = None):
-  """
-  Set the default time zone, by copying the correct time zone to /etc/localtime and by setting the /etc/localtime-copied-from symlink.
-  """
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  checkRoot()
-  if '/' in timezone and len(timezone.split('/')) == 2 and os.path.isfile('{0}/usr/share/zoneinfo/{1}'.format(mountPoint, timezone)):
-    copyfile('{0}/usr/share/zoneinfo/{1}'.format(mountPoint, timezone), '{0}/etc/localtime'.format(mountPoint))
-    if os.path.exists('{0}/etc/localtime-copied-from'.format(mountPoint)):
-      os.unlink('{0}/etc/localtime-copied-from'.format(mountPoint))
-    os.symlink('/usr/share/zoneinfo/{0}'.format(timezone), '{0}/etc/localtime-copied-from'.format(mountPoint))
-  else:
-    raise Exception('This time zone: ({0}), is incorrect.'.format(timezone))
-
-def isNTPEnabledByDefault(mountPoint = None):
-  """
-  ReturnsTrue if the NTP service is enabled by default.
-  To do this, the execute bit of /etc/rc.d/rc.ntpd is checked.
-  """
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  return os.access('{0}/etc/rc.d/rc.ntpd'.format(mountPoint), os.X_OK)
-
-def setNTPDefault(enabled, mountPoint = None):
-  """
-  Fix the configuration for the default NTP service to be activated on boot or not.
-  """
-  checkRoot()
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = '/'
-  if enabled:
-    os.chmod('{0}/etc/rc.d/rc.ntpd'.format(mountPoint), 0755)
-  else:
-    os.chmod('{0}/etc/rc.d/rc.ntpd'.format(mountPoint), 0644)
-
-# Unit test
-if __name__ == '__main__':
-  from assertPlus import *
-  checkRoot()
-  continents = listTZContinents()
-  assertTrue(type(continents) == list)
-  assertTrue(len(continents) > 0)
-  assertTrue('Europe' in continents)
-  cities = listTZCities('Europe')
-  assertTrue(type(cities) == list)
-  assertTrue(len(cities) > 0)
-  assertTrue('Paris' in cities)
-  tz = listTimeZones()
-  assertTrue(type(tz) == dict)
-  assertTrue(len(tz) > 0)
-  assertTrue('Europe' in tz)
-  assertTrue('Paris' in tz['Europe'])
-  deftz = getDefaultTimeZone()
-  assertTrue('/' in deftz)
-  assertEquals(2, len(deftz.split('/')))
-  setDefaultTimeZone('Etc/Zulu')
-  tz = getDefaultTimeZone()
-  assertTrue('/' in tz)
-  assertEquals(2, len(tz.split('/')))
-  assertEquals('Etc/Zulu', tz)
-  setDefaultTimeZone(deftz)
-  ntp = isNTPEnabledByDefault()
-  assertTrue(type(ntp) == bool)
-  setNTPDefault(True)
-  assertTrue(isNTPEnabledByDefault())
-  setNTPDefault(False)
-  assertFalse(isNTPEnabledByDefault())
-  setNTPDefault(ntp)
diff --git a/src/lib/salix_livetools_library/user.py b/src/lib/salix_livetools_library/user.py
deleted file mode 100644 (file)
index 96a5ea9..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-"""
-Functions to handle users and groups:
-  - listRegularSystemUsers
-  - createSystemUser
-  - changePasswordSystemUser
-  - checkPasswordSystemUser
-  - deleteSystemUser
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2011-2013, Salix OS'
-__license__ = 'GPL2+'
-from execute import *
-from chroot import *
-import os
-import random
-import string
-import crypt
-
-_minUIDForRegularUser = 1000
-
-def listRegularSystemUsers(mountPoint = None):
-  """
-  Returns a sorted list of regular users, i.e. users with id ≥ 1000.
-  """
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  ret = []
-  for line in open('{0}/etc/passwd'.format(mountPoint), 'rb').read().decode('utf-8').splitlines():
-    user, _, uid, _ = line.split(':', 3)
-    if int(uid) >= _minUIDForRegularUser:
-      ret.append(user)
-  return sorted(ret)
-
-def createSystemUser(user, group = '', groups = [
-  'audio',
-  'cdrom',
-  'games',
-  'floppy',
-  'lp',
-  'netdev',
-  'plugdev',
-  'power',
-  'scanner',
-  'video' ], password = None, shell = '/bin/bash', mountPoint = None):
-  """
-  Creates a user 'user' in the system under the 'mountPoint'.
-  If the 'group' is specified, and is different than __default__, it will be used.
-  If the 'group' is '' then the default group will be used.
-  If the 'group' is None then a group of the same name of the user will be created for the new user to be part of.
-  The newly created user will also be part of the specified 'groups'.
-  'password' is clear text password that will be used. If not specified, the password will be deactivated for the created user.
-  See "man useradd" for more information.
-  """
-  checkRoot()
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  cmd = ['/usr/sbin/useradd', '-m']
-  if group == '':
-    cmd.append('-N')
-  elif group:
-    cmd.append('-N')
-    cmd.append('-g')
-    cmd.append(group)
-  else:
-    cmd.append('-U')
-  if groups:
-    cmd.append('-G')
-    cmd.append(','.join(groups))
-  if password:
-    salt = '$1${0}$'.format(''.join(random.Random().sample(string.ascii_letters + string.digits, 8)))
-    cryptedPwd = crypt.crypt(password, salt)
-    cmd.append('-p')
-    cmd.append(cryptedPwd)
-  if shell:
-    cmd.append('-s')
-    cmd.append(shell)
-  cmd.append(user)
-  if not mountPoint or mountPoint == '/':
-    return execCall(cmd, shell = False)
-  else:
-    return execChroot(mountPoint, cmd)
-
-def changePasswordSystemUser(user, password, mountPoint = None):
-  checkRoot()
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if not password:
-    raise Exception('A password must be provided.')
-  salt = '$1${0}$'.format(''.join(random.Random().sample(string.ascii_letters + string.digits, 8)))
-  cryptedPwd = crypt.crypt(password, salt)
-  cmd = "echo '{0}:{1}' | /usr/sbin/chpasswd -e".format(user, cryptedPwd)
-  if not mountPoint or mountPoint == '/':
-    return execCall(cmd, shell = True)
-  else:
-    return execChroot(mountPoint, cmd, shell = True)
-
-def checkPasswordSystemUser(user, password, mountPoint = None):
-  checkRoot()
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  if mountPoint == None:
-    mountPoint = ''
-  if not password:
-    raise Exception('A password must be provided.')
-  users = dict([tuple(line.split(':', 2)[0:2]) for line in open('{0}/etc/shadow'.format(mountPoint), 'r').read().decode('utf-8').splitlines()])
-  if user in users:
-    cryptedPwd = users[user]
-    salt = cryptedPwd[:cryptedPwd.rfind('$') + 1]
-    return cryptedPwd == crypt.crypt(password, salt)
-  else:
-    return False
-
-def deleteSystemUser(user, mountPoint = None):
-  """
-  Removes the specified 'user' from the system under the 'mountPoint'.
-  """
-  checkRoot()
-  if mountPoint and not os.path.isdir(mountPoint):
-    raise IOError("'{0}' does not exist or is not a directory.".format(mountPoint))
-  cmd = ['/usr/sbin/userdel', '-r', user]
-  if not mountPoint or mountPoint == '/':
-    return execCall(cmd, shell = False)
-  else:
-    return execChroot(mountPoint, cmd)
-
-# Unit test
-if __name__ == '__main__':
-  from assertPlus import *
-  checkRoot()
-  users = listRegularSystemUsers()
-  print ' '.join(users)
-  assertTrue(len(users) > 0)
-  testUser = '__test__'
-  assertEquals(0, createSystemUser(testUser, password = 'test'))
-  assertTrue(testUser in listRegularSystemUsers())
-  assertTrue(checkPasswordSystemUser(testUser, 'test', mountPoint = '/'))
-  assertFalse(checkPasswordSystemUser(testUser, 'test2'))
-  assertEquals(0, deleteSystemUser(testUser))
-  assertFalse(testUser in listRegularSystemUsers())
-  assertEquals(0, createSystemUser(testUser, mountPoint = '/./')) # to be different than '/' and to really force the chroot ;-)
-  assertTrue(testUser in listRegularSystemUsers())
-  assertEquals(0, changePasswordSystemUser(testUser, 'test'))
-  assertTrue(checkPasswordSystemUser(testUser, 'test'))
-  assertFalse(checkPasswordSystemUser(testUser, 'test3'))
-  assertEquals(0, deleteSystemUser(testUser, mountPoint = '/./'))
-  assertFalse(testUser in listRegularSystemUsers())
diff --git a/src/lib/testurwidmore.py b/src/lib/testurwidmore.py
deleted file mode 100755 (executable)
index 00be263..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
-from __future__ import unicode_literals
-
-import urwid_more as urwidm
-import time
-palette = [
-    ('body', 'light gray', 'black'),
-    ('header', 'white', 'dark blue'),
-    ('footer', 'light green', 'black'),
-    ('footer_key', 'yellow', 'black'),
-    ('strong', 'white', 'black'),
-    ('copyright', 'light blue', 'black'),
-    ('authors', 'light cyan', 'black'),
-    ('translators', 'light green', 'black'),
-    ('focusable', 'light green', 'black'),
-    ('unfocusable', 'dark blue', 'black'),
-    ('focus', 'black', 'dark green'),
-    ('focus_edit', 'yellow', 'black'),
-    ('focus_icon', 'yellow', 'black'),
-    ('focus_radio', 'yellow', 'black'),
-    ('focus_combo', 'black', 'dark green'),
-    ('comboitem', 'light gray', 'dark blue'),
-    ('comboitem_focus', 'black', 'brown'),
-    ('error', 'white', 'dark red'),
-    ('focus_error', 'light red', 'black'),
-    ('important', 'yellow', 'black', 'bold'),
-    ('info', 'white', 'dark blue', 'bold'),
-    ('error', 'white', 'dark red', 'bold'),
-  ]
-ui = urwidm.raw_display.Screen()
-ui.set_mouse_tracking()
-def handleKeys(key):
-  if not isinstance(key, tuple): # only keyboard input
-    key = key.lower()
-    if key in ('q', 'f10'):
-      raise urwidm.ExitMainLoop()
-def focusGain(widget, context):
-  print("\nFocus Gain on help {0} ({1})".format(context, widget))
-  time.sleep(1)
-  return True
-def focusLost(widget, context):
-  print("\nFocus Lost on help {0} ({1})".format(context, widget))
-  time.sleep(1)
-  return True
-def connectFocus(widget, context):
-  urwidm.connect_signal(widget, 'focusgain', focusGain, context)
-  urwidm.connect_signal(widget, 'focuslost', focusLost, context)
-
-header = urwidm.Text('Title')
-footer = urwidm.Text('Q or F10 to quit')
-
-btn1 = urwidm.ButtonMore('btn1')
-connectFocus(btn1, 'btn1')
-
-btn2 = urwidm.ButtonMore('btn2')
-connectFocus(btn2, 'btn2')
-
-edit = urwidm.EditMore('edit')
-connectFocus(edit, 'edit')
-
-cb = urwidm.CheckBoxMore('checkbox')
-connectFocus(cb, 'checkbox')
-
-radioGrp = []
-radio1 = urwidm.RadioButtonMore(radioGrp, 'radio1')
-connectFocus(radio1, 'radio1')
-radio2 = urwidm.RadioButtonMore(radioGrp, 'radio2')
-connectFocus(radio2, 'radio2')
-cols = urwidm.ColumnsMore([radio1, radio2])
-connectFocus(cols, 'cols')
-
-txt1 = urwidm.TextMore('text1')
-btn3 = urwidm.ButtonMore('btn3')
-connectFocus(btn3, 'btn3')
-btn4 = urwidm.ButtonMore('btn4')
-connectFocus(btn4, 'btn4')
-gf = urwidm.GridFlowMore([txt1, btn3, btn4], 10, 2, 2, 'center')
-connectFocus(gf, 'gf')
-
-btn5 = urwidm.ButtonMore('btn5')
-connectFocus(btn5, 'btn5')
-cb = urwidm.ComboBox('combo', ['item1', 'item2'])
-connectFocus(cb, 'cb')
-pile = urwidm.PileMore([btn5, cb], cb)
-connectFocus(pile, 'pile')
-cols2 = urwidm.ColumnsMore([urwidm.TextMore('text2'), pile])
-connectFocus(cols2, 'cols2')
-
-bodyList = [
-    cb,
-    cols2,
-    btn1,
-    cols,
-    btn2,
-    gf,
-  ]
-
-body = urwidm.ListBoxMore(urwidm.SimpleListWalker(bodyList))
-body.attr = 'body'
-frame = urwidm.FrameMore(body, header, footer, focus_part = 'body')
-frame.attr = 'body'
-mainView = frame
-loop = urwidm.MainLoop(mainView, palette, handle_mouse = True, unhandled_input = handleKeys, pop_ups = True)
-loop.run()
diff --git a/src/lib/urwid_more.py b/src/lib/urwid_more.py
deleted file mode 100644 (file)
index 0a95032..0000000
+++ /dev/null
@@ -1,1373 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set et ai sw=2 sts=2 ts=2 tw=0:
-"""
-More widgets for Urwid
-Based on the work on curses_misc.py in Wicd.
-"""
-from __future__ import unicode_literals
-
-__copyright__ = 'Copyright 2013-2014, Salix OS, 2008-2009 Andrew Psaltis'
-__license__ = 'GPL2+'
-
-from urwid import *
-from urwid.signals import _signals as urwid_signals
-from urwid.canvas import apply_text_layout as urwid_apply_text_layout
-from urwid.util import is_mouse_press as urwid_is_mouse_press
-from urwid.util import is_mouse_event as urwid_is_mouse_event
-import gettext
-import re
-try:
-  _
-except NameError:
-  gettext.install('')
-
-class FocusEventWidget(Widget):
-  signals = ['focusgain', 'focuslost'] # will be used by the metaclass of Widget to call register_signal
-  _has_focus = False
-  @property
-  def has_focus(self):
-    return self._has_focus
-  def _can_gain_focus(self):
-    return True
-  def _can_loose_focus(self):
-    return True
-  def get_focused_subwidget(self):
-    return None
-  def _can_gain_focus_rec(self):
-    ret = self._can_gain_focus()
-    subw = self.get_focused_subwidget()
-    if subw and isinstance(subw, FocusEventWidget):
-      ret &= subw._can_gain_focus_rec()
-    return ret
-  def _can_loose_focus_rec(self):
-    ret = self._can_loose_focus()
-    subw = self.get_focused_subwidget()
-    if subw and isinstance(subw, FocusEventWidget):
-      ret &= subw._can_loose_focus_rec()
-    return ret
-  def _emit_focus_event(self, name, *args):
-    """
-    Return True if there is no callback, or if all callback answer True
-    """
-    result = True
-    signal_obj = urwid_signals
-    d = getattr(self, signal_obj._signal_attr, {})
-    for callback, user_arg in d.get(name, []):
-      args_copy = [self]
-      args_copy.extend(args)
-      if user_arg is not None:
-        args_copy.append(user_arg)
-      result &= bool(callback(*args_copy))
-    return result
-  def _emit_focusgain(self):
-    """
-    Return True if there is no callback, or if all callback answer True
-    """
-    return self._emit_focus_event('focusgain')
-  def _emit_focuslost(self):
-    """
-    Return True if there is no callback, or if all callback answer True
-    """
-    return self._emit_focus_event('focuslost')
-  def _emit_focusgain_rec(self):
-    ret = self._emit_focusgain()
-    subw = self.get_focused_subwidget()
-    if subw and isinstance(subw, FocusEventWidget):
-      ret &= subw._emit_focusgain_rec()
-    return ret
-  def _emit_focuslost_rec(self):
-    ret = True
-    subw = self.get_focused_subwidget()
-    if subw and isinstance(subw, FocusEventWidget):
-      ret = subw._emit_focuslost_rec()
-    ret &= self._emit_focuslost()
-    return ret
-  def gain_focus(self):
-    ret = self._can_gain_focus_rec()
-    if ret:
-      ret = self._emit_focusgain_rec()
-    if ret:
-      self._has_focus = True
-    return ret
-  def loose_focus(self):
-    ret = self._can_loose_focus()
-    if ret:
-      ret = self._emit_focuslost_rec()
-    if ret:
-      self._has_focus = False
-    return ret
-
-class SensitiveWidgetBehavior(object):
-  """
-  Makes an object have mutable selectivity.
-  """
-  _default_sensitive_attr = ('focusable', 'focus')
-  """
-  sensitive_attr = tuple of (attr, focus_attr) when sensitive
-      attr = attribute to apply to w
-      focus_attr = attribute to apply when in focus, if None use attr
-  """
-  _default_unsensitive_attr = ('unfocusable', '')
-  """
-  unsensitive_attr = tuple of (attr, focus_attr) when not sensitive
-      attr = attribute to apply to w
-      focus_attr = attribute to apply when in focus, if None use attr
-  """
-
-  def __init__(self, state = True):
-    if hasattr(self, '_sensitive'):
-      return # already initialized
-    self._sensitive = state
-    self._sensitive_attr = self._default_sensitive_attr
-    self._unsensitive_attr = self._default_unsensitive_attr
-  def get_sensitive_attr(self):
-    return self._sensitive_attr
-  def set_sensitive_attr(self, attr):
-    if type(attr) != tuple:
-      attr = (attr, attr)
-    self._sensitive_attr = attr
-    self._invalidate()
-  sensitive_attr = property(get_sensitive_attr, set_sensitive_attr)
-  def get_unsensitive_attr(self):
-    return self._unsensitive_attr
-  def set_unsensitive_attr(self, attr):
-    if type(attr) != tuple:
-      attr = (attr, attr)
-    self._unsensitive_attr = attr
-    self._invalidate()
-  unsensitive_attr = property(get_unsensitive_attr, set_unsensitive_attr)
-  def get_attr(self):
-    return (self.sensitive_attr, self.unsensitive_attr)
-  def set_attr(self, attr):
-    if type(attr) != tuple:
-      attr = (attr, attr)
-    self.set_sensitive_attr(attr)
-    self.set_unsensitive_attr(attr)
-  attr = property(get_attr, set_attr)
-  def get_sensitive(self):
-    return self._sensitive
-  def set_sensitive(self, state):
-    self._sensitive = state
-    self._invalidate()
-  sensitive = property(get_sensitive, set_sensitive)
-  def selectable(self):
-    return self._selectable and self.sensitive
-  def canvas_with_attr(self, canvas, focus = False):
-    """ Taken from AttrMap """
-    new_canvas = CompositeCanvas(canvas)
-    if self.sensitive:
-      attr_tuple = self._sensitive_attr
-    else:
-      attr_tuple = self._unsensitive_attr
-    if focus and attr_tuple[1]:
-      attr_map = attr_tuple[1]
-    else:
-      attr_map = attr_tuple[0]
-    if type(attr_map) != dict:
-      attr_map = {None: attr_map}
-    new_canvas.fill_attr_apply(attr_map)
-    return new_canvas
-
-class More(FocusEventWidget, SensitiveWidgetBehavior):
-  """
-  Class that combine a FocusEventWidget and a SensitiveWidgetBehavior.
-  Parent of all other widgets defined here.
-  """
-  def __init__(self, sensitive = True):
-    SensitiveWidgetBehavior.__init__(self, sensitive)
-  def selectable(self):
-    """
-    Due to inheritance order, this will be the implementation of Widget,
-    pulled from FocusEventWidget that will be used.
-    So here we force to use the one from SensitiveWidgetBehavior
-    """
-    return SensitiveWidgetBehavior.selectable(self)
-
-class TextMore(More, Text):
-  _default_sensitive_attr = ('body', 'body')
-  def __init__(self, markup, align = LEFT, wrap = SPACE, layout = None):
-    More.__init__(self, False)
-    Text.__init__(self, markup, align, wrap, layout)
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(self.__super.render(size, focus = focus), focus)
-
-class EditMore(More, Edit):
-  _default_sensitive_attr = ('focusable', 'focus_edit')
-  def __init__(self, caption = "", edit_text = "", multiline = False, align = LEFT, wrap = SPACE, allow_tab = False, edit_pos = None, layout = None, mask = None):
-    More.__init__(self)
-    self._selectable = True
-    Edit.__init__(self, caption, edit_text, multiline, align, wrap, allow_tab, edit_pos, layout, mask)
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(self.__super.render(size, focus = focus), focus)
-
-class IntEditMore(More, IntEdit):
-  _default_sensitive_attr = ('focusable', 'focus_edit')
-  def __init__(self, caption = "", default = None):
-    More.__init__(self)
-    self._selectable = True
-    IntEdit.__init__(self, caption, default)
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(self.__super.render(size, focus = focus), focus)
-
-class SelectableIconMore(More, SelectableIcon):
-  _default_sensitive_attr = ('focusable', 'focus_icon')
-  def __init__(self, text, cursor_position = 1):
-    More.__init__(self)
-    self._selectable = True
-    SelectableIcon.__init__(self, text, cursor_position)
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(self.__super.render(size, focus = focus), focus)
-
-class ButtonMore(More, Button):
-  def __init__(self, label, on_press = None, user_data = None):
-    More.__init__(self)
-    self._selectable = True
-    Button.__init__(self, label, on_press, user_data)
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(self.__super.render(size, focus = focus), focus)
-
-class CheckBoxMore(More, CheckBox):
-  _default_sensitive_attr = ('focusable', 'focus_radio')
-  states = {
-    True: SelectableIconMore("[X]"),
-    False: SelectableIconMore("[ ]"),
-    'mixed': SelectableIconMore("[#]") }
-  def __init__(self, label, state = False, has_mixed = False, on_state_change = None, user_data = None):
-    More.__init__(self)
-    self._selectable = True
-    CheckBox.__init__(self, label, state, has_mixed, on_state_change, user_data)
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(self.__super.render(size, focus = focus), focus)
-
-class RadioButtonMore(More, RadioButton):
-  _default_sensitive_attr = ('focusable', 'focus_radio')
-  states = {
-    True: SelectableIconMore("(X)"),
-    False: SelectableIconMore("( )"),
-    'mixed': SelectableIconMore("(#)") }
-  def __init__(self, group, label, state = "first True", on_state_change = None, user_data = None):
-    More.__init__(self)
-    self._selectable = True
-    RadioButton.__init__(self, group, label, state, on_state_change, user_data)
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(self.__super.render(size, focus = focus), focus)
-
-class WidgetWrapMore(More, WidgetWrap):
-  def __init__(self, w):
-    More.__init__(self)
-    self._wrapped_widget = w
-  def _render_with_attr(self, size, focus = False):
-    canvas = self._w.render(size, focus = focus)
-    return self.canvas_with_attr(CompositeCanvas(canvas), focus)
-  def render(self, size, focus = False):
-    return self._render_with_attr(size, focus)
-  def selectable(self):
-    return self._w.selectable() and self.sensitive
-  def _can_gain_focus(self):
-    if isinstance(self._w, FocusEventWidget):
-      return self._w._can_gain_focus()
-    else:
-      return FocusEventWidget._can_gain_focus(self)
-  def _can_loose_focus(self):
-    if isinstance(self._w, FocusEventWidget):
-      return self._w._can_loose_focus()
-    else:
-      return FocusEventWidget._can_loose_focus(self)
-  def get_focused_subwidget(self):
-    return self._w
-
-class WidgetDecorationMore(More, WidgetDecoration):
-  def __init__(self, original_widget):
-    More.__init__(self)
-    WidgetDecoration.__init__(self, original_widget)
-  def _render_with_attr(self, size, focus = False):
-    canvas = self._original_widget.render(size, focus = focus)
-    return self.canvas_with_attr(CompositeCanvas(canvas), focus)
-  def render(self, size, focus = False):
-    return self._render_with_attr(size, focus)
-  def selectable(self):
-    return self._original_widget.selectable() and self.sensitive
-  def _can_gain_focus(self):
-    if isinstance(self._original_widget, FocusEventWidget):
-      return self._original_widget._can_gain_focus()
-    else:
-      return FocusEventWidget._can_gain_focus(self)
-  def _can_loose_focus(self):
-    if isinstance(self._original_widget, FocusEventWidget):
-      return self._original_widget._can_loose_focus()
-    else:
-      return FocusEventWidget._can_loose_focus(self)
-  def get_focused_subwidget(self):
-    return self._original_widget
-
-class WidgetPlaceholderMore(WidgetDecorationMore, WidgetPlaceholder):
-  _default_sensitive_attr = 'body'
-  _default_unsensitive_attr = 'body'
-  def __init__(self, original_widget):
-    WidgetDecorationMore.__init__(self, original_widget)
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(self.__super.render(size, focus = focus), focus)
-
-class AttrMapMore(WidgetDecorationMore, AttrMap):
-  def __init__(self, w, attr_map, focus_map = None):
-    WidgetDecorationMore.__init__(self, w)
-    AttrMap.__init__(self, w, attr_map, focus_map)
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(AttrMap.render(self, size, focus), focus)
-
-class AttrWrapMore(WidgetDecorationMore, AttrWrap):
-  def __init__(self, w, attr, focus_attr = None):
-    WidgetDecorationMore.__init__(self, w)
-    AttrWrap.__init__(self, w, attr, focus_attr)
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(AttrWrap.render(self, size, focus), focus)
-
-class PaddingMore(WidgetDecorationMore, Padding):
-  _default_sensitive_attr = 'body'
-  _default_unsensitive_attr = 'body'
-  def __init__(self, w, align = LEFT, width = PACK, min_width = None, left = 0, right = 0):
-    WidgetDecorationMore.__init__(self, w)
-    Padding.__init__(self, w, align, width, min_width, left, right)
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(Padding.render(self, size, focus), focus)
-
-class FillerMore(WidgetDecorationMore, Filler):
-  _default_sensitive_attr = 'body'
-  _default_unsensitive_attr = 'body'
-  def __init__(self, body, valign = "middle", height = None, min_height = None):
-    WidgetDecorationMore.__init__(self, body)
-    Filler.__init__(self, body, valign, height, min_height)
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(Filler.render(self, size, focus), focus)
-
-class BoxAdapterMore(WidgetDecorationMore, BoxAdapter):
-  _default_sensitive_attr = 'body'
-  _default_unsensitive_attr = 'body'
-  def __init__(self, box_widget, height):
-    WidgetDecorationMore.__init__(self, box_widget)
-    BoxAdapter.__init__(self, box_widget, height)
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(BoxAdapter.render(self, size, focus), focus)
-
-class WidgetContainerMore(More, WidgetContainer):
-  _default_sensitive_attr = 'body'
-  _default_unsensitive_attr = 'body'
-  def __init__(self, widget_list):
-    More.__init__(self)
-    WidgetContainer.__init__(self, widget_list)
-
-class FrameMore(More, Frame):
-  _default_sensitive_attr = 'body'
-  _default_unsensitive_attr = 'body'
-  _filler_widget_class = FillerMore
-  def __init__(self, body, header = None, footer = None, focus_part = 'body'):
-    More.__init__(self)
-    self._selectable = True
-    Frame.__init__(self, body, header, footer, focus_part)
-    self.set_focus('body')
-  def render(self, size, focus = False):
-    """Render frame and return it."""
-    (maxcol, maxrow) = size
-    (htrim, ftrim),(hrows, frows) = self.frame_top_bottom((maxcol, maxrow), focus)
-    combinelist = []
-    depends_on = []
-    head = None
-    if htrim and htrim < hrows:
-      head = self._filler_widget_class(self.header, 'top').render((maxcol, htrim), focus and self.focus_part == 'header')
-    elif htrim:
-      head = self.header.render((maxcol,), focus and self.focus_part == 'header')
-      assert head.rows() == hrows, "rows, render mismatch"
-    if head:
-      combinelist.append((head, 'header', self.focus_part == 'header'))
-      depends_on.append(self.header)
-    if ftrim + htrim < maxrow:
-      body = self.body.render((maxcol, maxrow - ftrim - htrim), focus and self.focus_part == 'body')
-      combinelist.append((body, 'body', self.focus_part == 'body'))
-      depends_on.append(self.body)
-      pass
-    foot = None
-    if ftrim and ftrim < frows:
-      foot = self._filler_widget_class(self.footer, 'bottom').render((maxcol, ftrim), focus and self.focus_part == 'footer')
-    elif ftrim:
-      foot = self.footer.render((maxcol,), focus and self.focus_part == 'footer')
-      assert foot.rows() == frows, "rows, render mismatch"
-    if foot:
-      combinelist.append((foot, 'footer', self.focus_part == 'footer'))
-      depends_on.append(self.footer)
-    return self.canvas_with_attr(CanvasCombine(combinelist), focus)
-    #return CanvasCombine(combinelist)
-  def _get_focus_widget(self, part):
-    assert part in ('header', 'footer', 'body')
-    if part == 'header':
-      focus_w = self.get_header()
-    elif part == 'footer':
-      focus_w = self.get_footer()
-    else: # part == 'body'
-      focus_w = self.get_body()
-    return focus_w
-  def set_focus(self, part):
-    """
-    Set the part of the frame that is in focus.
-    part -- 'header', 'footer' or 'body'
-    """
-    assert part in ('header', 'footer', 'body')
-    ok = True
-    if self.has_focus:
-      focus_w = self._get_focus_widget(self.get_focus())
-      if focus_w and isinstance(focus_w, FocusEventWidget):
-        ok = focus_w.loose_focus()
-      if ok:
-        focus_w = self._get_focus_widget(part)
-        if isinstance(focus_w, FocusEventWidget):
-          ok = focus_w.gain_focus()
-    if ok:
-      Frame.set_focus(self, part)
-  def get_focused_subwidget(self):
-    return self._get_focus_widget(self.get_focus())
-
-class PileMore(More, Pile):
-  _default_sensitive_attr = 'body'
-  _default_unsensitive_attr = 'body'
-  def __init__(self, widget_list, focus_item = None):
-    More.__init__(self)
-    Pile.__init__(self, widget_list, focus_item)
-  def selectable(self):
-    return Pile.selectable(self) and self.sensitive
-  def keypress(self, size, key):
-    """
-    Pass the keypress to the widget in focus.
-    Unhandled 'up' and 'down' keys may cause a focus change.
-    Copied from original Pile but with custom focus event handling.
-    """
-    item_rows = None
-    if len(size) == 2:
-      item_rows = self.get_item_rows(size, focus = True)
-    i = self.widget_list.index(self.focus_item)
-    f, height = self._get_item_types(i)
-    if self.focus_item.selectable():
-      tsize = self.get_item_size(size, i, True, item_rows)
-      key = self.focus_item.keypress(tsize, key)
-      if self._command_map[key] not in ('cursor up', 'cursor down'):
-        return key
-    if self._command_map[key] == 'cursor up':
-      candidates = range(i - 1, -1, -1) # count backwards to 0
-    else: # self._command_map[key] == 'cursor down'
-      candidates = range(i + 1, len(self.widget_list))
-    if not item_rows:
-      item_rows = self.get_item_rows(size, focus = True)
-    for j in candidates:
-      if not self.widget_list[j].selectable():
-        continue
-      self._update_pref_col_from_focus(size)
-      old_focus = self.focus_item
-      self.set_focus(j)
-      if old_focus == self.focus_item: # focus change has been denied
-        return
-      if not hasattr(self.focus_item,'move_cursor_to_coords'):
-        return
-      f, height = self._get_item_types(i)
-      rows = item_rows[j]
-      if self._command_map[key] == 'cursor up':
-        rowlist = range(rows-1, -1, -1)
-      else: # self._command_map[key] == 'cursor down'
-        rowlist = range(rows)
-      for row in rowlist:
-        tsize=self.get_item_size(size,j,True,item_rows)
-        if self.focus_item.move_cursor_to_coords(tsize, self.pref_col, row):
-          break
-      return
-    # nothing to select
-    return key
-  def set_focus(self, item):
-    """
-    Set the item in focus.
-    item -- widget or integer index
-    """
-    ok = True
-    if not hasattr(self, "focus_item"):
-      Pile.set_focus(self, item)
-    if self.focus_item:
-      focus_w = self.get_focus()
-      if type(item) == int:
-        new_focus_w = self.widget_list[item]
-      else:
-        new_focus_w = item
-      if focus_w != new_focus_w:
-        if focus_w and isinstance(focus_w, FocusEventWidget):
-          ok = focus_w.loose_focus()
-        if ok:
-          if isinstance(new_focus_w, FocusEventWidget):
-            ok = new_focus_w.gain_focus()
-    if ok:
-      Pile.set_focus(self, item)
-  def get_focused_subwidget(self):
-    return self.get_focus()
-  def get_focus_pos(self):
-    """
-    Return the 1-based position of the select item in focus or 0 if none.
-    """
-    fw = self.get_focus()
-    pos = 0
-    if fw:
-      pos = self.widget_list.index(fw)
-    return pos
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(self.__super.render(size, focus), focus)
-
-class ColumnsMore(More, Columns):
-  _default_sensitive_attr = 'body'
-  _default_unsensitive_attr = 'body'
-  def __init__(self, widget_list, dividechars = 0, focus_column = None, min_width = 1, box_columns = None):
-    More.__init__(self)
-    Columns.__init__(self, widget_list, dividechars, focus_column, min_width, box_columns)
-  def selectable(self):
-    return Columns.selectable(self) and self.sensitive
-  def keypress(self, size, key):
-    ret = None
-    if self.focus_col is None:
-      ret = key
-    else:
-      widths = self.column_widths(size)
-      if self.focus_col < 0 or self.focus_col >= len(widths):
-        ret = key
-      else:
-        i = self.focus_col
-        mc = widths[i]
-        w = self.widget_list[i]
-        if self._command_map[key] not in ('cursor up', 'cursor down', 'cursor page up', 'cursor page down'):
-          self.pref_col = None
-        key = w.keypress((mc,) + size[1:], key)
-        if self._command_map[key] not in ('cursor left', 'cursor right'):
-          ret = key
-        else:
-          if self._command_map[key] == 'cursor left':
-            candidates = range(i-1, -1, -1) # count backwards to 0
-          else: # key == 'right'
-            candidates = range(i+1, len(widths))
-          for j in candidates:
-            if not self.widget_list[j].selectable():
-              continue
-            self.set_focus_column(j)
-            #ret = None
-            ret = key
-            break
-          else:
-            ret = key
-    return ret
-  def set_focus_column(self, num):
-    """Set the column in focus by its index in self.widget_list."""
-    ok = True
-    if self.get_focus_column():
-      focus_w = self.get_focus()
-      if isinstance(focus_w, FocusEventWidget):
-        ok = focus_w.loose_focus()
-    if ok:
-      focus_w = self.widget_list[num]
-      if isinstance(focus_w, FocusEventWidget):
-        ok = focus_w.gain_focus()
-    if ok:
-      Columns.set_focus_column(self, num)
-  def set_focus(self, item):
-    """Set the item in focus. item -- widget or integer index"""
-    if type(item) == int:
-      assert item>=0 and item<len(self.widget_list)
-      position = item
-    else:
-      position = self.widget_list.index(item)
-    ok = True
-    if self.has_focus:
-      if self.get_focus_column():
-        focus_w = self.get_focus()
-        if isinstance(focus_w, FocusEventWidget):
-          ok = focus_w.loose_focus()
-      if ok:
-        focus_w = self.widget_list[position]
-        if isinstance(focus_w, FocusEventWidget):
-          ok = focus_w.gain_focus()
-    if ok:
-      self.focus_col = position
-      self._invalidate()
-    return ok
-  def get_focused_subwidget(self):
-    return self.get_focus()
-  def mouse_event(self, size, event, button, col, row, focus):
-    """
-    Send event to appropriate column.
-    May change focus on button 1 press.
-    """
-    widths = self.column_widths(size)
-    x = 0
-    for i in range(len(widths)):
-      if col < x:
-        return False
-      w = self.widget_list[i]
-      end = x + widths[i]
-      if col >= end:
-        x = end + self.dividechars
-        continue
-      focus = focus and self.focus_col == i
-      ok = True
-      if urwid_is_mouse_press(event) and button == 1:
-        if w.selectable():
-          ok = self.set_focus(w)
-      if not ok or not hasattr(w,'mouse_event'):
-        return False
-      return w.mouse_event((end-x,)+size[1:], event, button, col - x, row, focus)
-    return False
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(self.__super.render(size, focus), focus)
-
-class GridFlowMore(More, GridFlow):
-  _default_sensitive_attr = 'body'
-  _default_unsensitive_attr = 'body'
-  _column_widget_class = ColumnsMore
-  _padding_widget_class = PaddingMore
-  _pile_widget_class = PileMore
-  def __init__(self, cells, cell_width, h_sep, v_sep, align):
-    More.__init__(self)
-    GridFlow.__init__(self, cells, cell_width, h_sep, v_sep, align)
-    for cell in cells:
-      if cell.selectable():
-        self.focus_cell = cell
-        break
-  def selectable(self):
-    return GridFlow.selectable(self) and self.sensitive
-  def generate_display_widget(self, size):
-    """
-    Actually generate display widget (ignoring cache)
-    Copied from original GridFlow but with custom sub-widgets.
-    """
-    (maxcol,) = size
-    d = Divider() # don't customize Divider, it's really a basic class.
-    if len(self.cells) == 0: # how dull
-      return d
-    if self.v_sep > 1:
-      # increase size of divider
-      d.top = self.v_sep-1
-    # cells per row
-    bpr = (maxcol+self.h_sep) // (self.cell_width+self.h_sep)
-    if bpr == 0: # too narrow, pile them on top of eachother
-      l = [self.cells[0]]
-      f = 0
-      for b in self.cells[1:]:
-        if b is self.focus_cell:
-          f = len(l)
-        if self.v_sep:
-          l.append(d)
-        l.append(b)
-      return self._pile_widget_class(l, f)
-    if bpr >= len(self.cells): # all fit on one row
-      k = len(self.cells)
-      f = self.cells.index(self.focus_cell)
-      cols = self._column_widget_class(self.cells, self.h_sep, f)
-      rwidth = (self.cell_width+self.h_sep)*k - self.h_sep
-      row = self._padding_widget_class(cols, self.align, rwidth)
-      return row
-    out = []
-    s = 0
-    f = 0
-    while s < len(self.cells):
-      if out and self.v_sep:
-        out.append(d)
-      k = min(len(self.cells), s + bpr)
-      cells = self.cells[s:k]
-      if self.focus_cell in cells:
-        f = len(out)
-        fcol = cells.index(self.focus_cell)
-        cols = self._column_widget_class(cells, self.h_sep, fcol)
-      else:
-        cols = self._column_widget_class(cells, self.h_sep)
-      rwidth = (self.cell_width+self.h_sep)*(k-s)-self.h_sep
-      row = self._padding_widget_class(cols, self.align, rwidth)
-      out.append(row)
-      s += bpr
-    return self._pile_widget_class(out, f)
-  def set_focus(self, cell):
-    """
-    Set the cell in focus.
-    cell -- widget or integer index into self.cells
-    """
-    ok = True
-    if self.has_focus:
-      focus_w = self.get_focus()
-      if type(cell) == int:
-        focus_w_next = self.cells[cell]
-      else:
-        focus_w_next = cell
-      if focus_w and focus_w != focus_w_next and isinstance(focus_w, FocusEventWidget):
-        ok = focus_w.loose_focus()
-        if ok and isinstance(focus_w_next, FocusEventWidget):
-          ok = focus_w_next.gain_focus()
-    if ok:
-      GridFlow.set_focus(self, cell)
-  def get_focused_subwidget(self):
-    return self.get_focus()
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(self.__super.render(size, focus), focus)
-
-class OverlayMore(More, Overlay):
-  _default_sensitive_attr = 'body'
-  _default_unsensitive_attr = 'body'
-  def __init__(self, top_w, bottom_w, align, width, valign, height, min_width = None, min_height = None):
-    More.__init__(self)
-    Overlay.__init__(self, top_w, bottom_w, align, width, valign, height, min_width, min_height)
-  def selectable(self):
-    return Overlay.selectable(self) and self.sensitive
-  def get_focused_subwidget(self):
-    return self.top_w
-  def render(self, size, focus = False):
-    return self.canvas_with_attr(self.__super.render(size, focus), focus)
-
-class ListBoxMore(More, ListBox):
-  _default_sensitive_attr = 'body'
-  _default_unsensitive_attr = 'body'
-  def __init__(self, body):
-    More.__init__(self)
-    self._selectable = True
-    ListBox.__init__(self, body)
-  def change_focus(self, size, position, offset_inset = 0, coming_from = None, cursor_coords = None, snap_rows = None):
-    old_widget, old_focus_pos = self.body.get_focus()
-    new_focus_pos = position
-    # hack for found the current widget in the list walker.
-    new_widget = self.body.get_next(new_focus_pos - 1)[0]
-    ok = True
-    if isinstance(old_widget, FocusEventWidget):
-      ok = old_widget.loose_focus()
-    if ok and isinstance(new_widget, FocusEventWidget):
-      ok = new_widget.gain_focus()
-    if ok:
-      ListBox.change_focus(self, size, position, offset_inset, coming_from, cursor_coords, snap_rows)
-    return ok
-  def get_focused_subwidget(self):
-    return self.get_focus()[0]
-  def mouse_event(self, size, event, button, col, row, focus):
-    """
-    Pass the event to the contained widgets.
-    May change focus on button 1 press.
-    """
-    (maxcol, maxrow) = size
-    middle, top, bottom = self.calculate_visible((maxcol, maxrow), focus = True)
-    if middle is None:
-      return False
-    _ignore, focus_widget, focus_pos, focus_rows, cursor = middle
-    trim_top, fill_above = top
-    _ignore, fill_below = bottom
-    fill_above.reverse() # fill_above is in bottom-up order
-    w_list = (fill_above + [(focus_widget, focus_pos, focus_rows)] + fill_below)
-    wrow = -trim_top
-    for w, w_pos, w_rows in w_list:
-      if wrow + w_rows > row:
-        break
-      wrow += w_rows
-    else:
-      return False
-    focus = focus and w == focus_widget
-    ret = False
-    if urwid_is_mouse_press(event) and button == 1:
-      if w.selectable():
-        ret = self.change_focus((maxcol, maxrow), w_pos, wrow)
-      else:
-        ret = True
-    if ret and hasattr(w, 'mouse_event'):
-      return w.mouse_event((maxcol,), event, button, col, row - wrow, focus)
-    else:
-      return False
-  def render(self, size, focus = False):
-    # hack to trigger a focus_gain on the first selectable widget
-    if self.set_focus_pending == 'first selectable':
-      for i, w in enumerate(self.body):
-        if w.selectable():
-          self.change_focus(size, i)
-          break
-    # render with attribute wrapping
-    return self.canvas_with_attr(self.__super.render(size, focus), focus)
-
-class LineBoxMore(WidgetDecorationMore, LineBox):
-  def __init__(self, original_widget, title = "", tlcorner = '┌', tline = '─', lline = '│', trcorner = '┐', blcorner = '└', rline = '│', bline = '─', brcorner = '┘'):
-    """See LineBox"""
-    self._tline, self._bline = AttrMapMore(Divider(tline), None), AttrMapMore(Divider(bline), None)
-    self._lline, self._rline = AttrMapMore(SolidFill(lline), None), AttrMapMore(SolidFill(rline), None)
-    self._tlcorner, self._trcorner = TextMore(tlcorner), TextMore(trcorner)
-    self._blcorner, self._brcorner = TextMore(blcorner), TextMore(brcorner)
-    self.title_widget = TextMore(self.format_title(title))
-    self.tline_widget = ColumnsMore([
-      self._tline,
-      ('flow', self.title_widget),
-      self._tline,
-    ])
-    top = ColumnsMore([
-      ('fixed', 1, self._tlcorner),
-      self.tline_widget,
-      ('fixed', 1, self._trcorner)
-    ])
-    middle = ColumnsMore([
-      ('fixed', 1, self._lline),
-      original_widget,
-      ('fixed', 1, self._rline),
-    ], box_columns = [0, 2], focus_column = 1)
-    bottom = ColumnsMore([
-      ('fixed', 1, self._blcorner), self._bline, ('fixed', 1, self._brcorner)
-    ])
-    pile = PileMore([('flow', top), middle, ('flow', bottom)], focus_item = 1)
-    WidgetDecorationMore.__init__(self, original_widget)
-    WidgetWrap.__init__(self, pile)
-  def render(self, size, focus = False):
-    for w in (self._tline, self._bline, self._lline, self._rline, self._tlcorner, self._trcorner, self._blcorner, self._brcorner, self.title_widget):
-      if self.sensitive:
-        w.attr = self.attr[0]
-      else:
-        w.attr = self.attr[1]
-    return self.canvas_with_attr(LineBox.render(self, size, focus), focus)
-
-class PopUpLauncherMore(WidgetDecorationMore, PopUpLauncher):
-  def __init__(self, original_widget):
-    WidgetDecorationMore.__init__(self, original_widget)
-    self._pop_up_widget = None
-  def render(self, size, focus = False):
-    canv = WidgetDecorationMore.render(self, size, focus)
-    if self._pop_up_widget:
-      canv = CompositeCanvas(canv)
-      canv.set_pop_up(self._pop_up_widget, **self.get_pop_up_parameters())
-    return canv
-
-class SelText(TextMore):
-  """A selectable text widget. See Text and TextMore."""
-  _default_sensitive_attr = ('focusable', 'focus_edit')
-  def __init__(self, markup, align = LEFT, wrap = SPACE, layout = None):
-    self.__super.__init__(markup, align, wrap, layout)
-    self._selectable = True
-    self.set_sensitive(True)
-  def keypress(self, size, key):
-    """Don't handle any keys."""
-    return key
-
-class ComboBox(PopUpLauncherMore):
-  """A ComboBox of text objects"""
-  class ComboSpace(WidgetWrapMore):
-    """The actual menu-like space that comes down from the ComboBox"""
-    signals = ['close', 'validate']
-    def __init__(self, items, show_first = 0, item_attrs = ('comboitem', 'comboitem_focus')):
-      """
-      items     : stuff to include in the combobox
-      show_first: index of the element in the list to pick first
-      """
-      normal_attr = item_attrs[0]
-      focus_attr = item_attrs[1]
-      sepLeft = AttrMapMore(SolidFill("│"), normal_attr)
-      sepRight = AttrMapMore(SolidFill("│"), normal_attr)
-      sepBottomLeft = AttrMapMore(Text("└"), normal_attr)
-      sepBottomRight = AttrMapMore(Text("┘"), normal_attr)
-      sepBottomCenter = AttrMapMore(Divider("─"), normal_attr)
-      self._content = []
-      for item in items:
-        if isinstance(item, Widget):
-          if item.selectable and hasattr(item, "text") and hasattr(item, "attr"): # duck typing
-            self._content.append(item)
-          else:
-            raise ValueError, "items in ComboBox should be strings or selectable widget with a text and attr properties"
-        else:
-          self._content.append(SelText(item))
-      self._listw = PileMore(self._content)
-      if show_first is None:
-        show_first = 0
-      self.set_selected_pos(show_first)
-      columns = ColumnsMore([
-        ('fixed', 1, PileMore([BoxAdapter(sepLeft, len(items)), sepBottomLeft])),
-        PileMore([self._listw, sepBottomCenter]),
-        ('fixed', 1, PileMore([BoxAdapter(sepRight, len(items)), sepBottomRight])),
-      ])
-      filler = FillerMore(columns)
-      self.__super.__init__(filler)
-      self._selectable = True
-      self._deco = [sepLeft, sepRight, sepBottomLeft, sepBottomRight, sepBottomCenter, self._listw]
-      self.set_item_attrs(item_attrs)
-    def get_size(self):
-      maxw = 1
-      maxh = 0
-      for widget in self._content:
-        w = 0
-        h = 0
-        for s in (None, ()):
-          try:
-            (w, h) = widget.pack(s)
-          except:
-            pass
-        maxw = max(maxw, w + 1)
-        maxh += h
-      return (maxw + 2, maxh + 1)
-    def set_item_attrs(self, item_attrs):
-      for w in self._content:
-        if hasattr(w, "attr"):
-          w.attr = item_attrs
-      w.attr = item_attrs
-      for w in self._deco:
-        w.attr = item_attrs
-    def keypress(self, size, key):
-      if key in 'esc':
-        self.close()
-      if key in ('enter', ' '):
-        self.validate()
-      else:
-        return self.__super.keypress(size, key)
-    def mouse_event(self, size, event, button, col, row, focus):
-      ret = self.__super.mouse_event(size, event, button, col, row, focus)
-      if urwid_is_mouse_press(event) and button == 1 and col > 1 and col < size[0] - 1 and row < size[1] - 1:
-        self.validate()
-      return ret
-    def close(self):
-      self.set_selected_pos(None)
-      self._emit('close')
-    def validate(self):
-      self.set_selected_item(self._listw.get_focus())
-      self._emit('validate')
-    def get_selected_item(self):
-      return self._selected_item
-    def set_selected_item(self, item):
-      try:
-        pos = [i.text for i in self._content].index(item.text)
-      except:
-        pos = None
-      self.set_selected_pos(pos)
-    selected_item = property(get_selected_item, set_selected_item)
-    def get_selected_pos(self):
-      return self._selected_pos
-    def set_selected_pos(self, pos):
-      if pos is not None and pos < len(self._content):
-        self._listw.set_focus(pos)
-        self._selected_item = self._content[pos].text
-        self._selected_pos = pos
-      else:
-        self._selected_item = None
-        self._selected_pos = None
-    selected_pos = property(get_selected_pos, set_selected_pos)
-
-  _default_sensitive_attr = ('body', '')
-  _default_unsensitive_attr = ('body', '')
-  DOWN_ARROW = "↓"
-  signals = ['displaycombo', 'change']
-
-  def __init__(self, label = '', items = None, use_enter = True, focus_index = 0):
-    """
-    label       : bit of text that preceeds the combobox.  If it is "", then ignore it
-    items       : stuff to include in the combobox
-    use_enter   : does enter trigger the combo list
-    focus_index : index of the element in the list to pick first
-    """
-    self.label = Text(label)
-    if items is None:
-      items = []
-      focus_index = None
-    self.cbox = self._create_cbox_widget()
-    if label:
-      w = ColumnsMore(
-        [
-          ('fixed', len(label), self.label),
-          ('fixed', 1, self.cbox),
-          ('fixed', len(self.DOWN_ARROW), Text(self.DOWN_ARROW))
-        ], dividechars = 1)
-    else:
-      w = ColumnsMore(
-        [
-          ('fixed', 1, self.cbox),
-          ('fixed', len(self.DOWN_ARROW), Text(self.DOWN_ARROW))
-        ], dividechars = 1)
-    self.__super.__init__(w)
-    self.combo_attrs = ('comboitem', 'comboitem_focus')
-    self.use_enter = use_enter
-    self.set_list(items)
-    self.set_selected_item(focus_index)
-    self._overlay_left = 0
-    self._overlay_width = len(self.DOWN_ARROW)
-    self._overlay_height = len(items)
-    connect_signal(self, 'displaycombo', self.displaycombo)
-  def _create_cbox_widget(self):
-    return SelText('')
-  def _set_cbox_text(self, text):
-    ok = False
-    self.cbox._fromCombo = True # add a property to say that we set the text from the combo
-    if not ok and hasattr(self.cbox, "set_text"):
-      try:
-        self.cbox.set_text(text)
-        ok = True
-      except:
-        pass
-    if not ok and hasattr(self.cbox, "set_edit_text"):
-      try:
-        self.cbox.set_edit_text(text)
-        ok = True
-      except:
-        pass
-    self.cbox._fromCombo = False
-    if not ok:
-      raise Exception, "Do not know how to set the text in the widget {0}".format(self.cbox)
-  def _item_text(self, item):
-    if isinstance(item, basestring):
-      return item
-    else:
-      return item.text
-  def get_selected_item(self):
-    """ Return (text, index) or (text, None) if the selected text is not in the list """
-    curr_text = self.cbox.text
-    try:
-      index = [self._item_text(i) for i in self.list].index(curr_text)
-    except:
-      index = None
-    return (curr_text, index)
-  def set_selected_item(self, index):
-    """ Set widget focus. """
-    if index is not None and isinstance(index, int):
-      curr_text = self._item_text(self.list[index])
-    elif index is not None and isinstance(index, basestring):
-      curr_text = text
-    else:
-      curr_text = ''
-    self._set_cbox_text(curr_text)
-  selected_item = property(get_selected_item, set_selected_item)
-  def get_sensitive(self):
-    return self.cbox.get_sensitive()
-  def set_sensitive(self, state):
-    self.cbox.set_sensitive(state)
-  def selectable(self):
-    return self.cbox.selectable()
-  def get_list(self):
-    return self._list
-  def set_list(self, items):
-    self._list = items
-    maxw = reduce(max, [len(self._item_text(item)) for item in self._list], 0) + 1
-    zonepos = 0
-    if self.label.text:
-      zonepos = 1
-    self._original_widget.column_types[zonepos] = ('fixed', maxw)
-  list = property(get_list, set_list)
-  def set_combo_attrs(self, normal_attr, focus_attr):
-    self.combo_attrs = (normal_attr, focus_attr)
-  def keypress(self, size, key):
-    """
-    If we press space or enter, be a combo box!
-    """
-    if key == ' ' or (self.use_enter and key == 'enter'):
-      self._emit("displaycombo")
-    else:
-      return self._original_widget.keypress(size, key)
-  def mouse_event(self, size, event, button, col, row, focus):
-    maxcol = size[0]
-    maxtxt = len(self.cbox.text) + 1
-    if self.label.text:
-      maxtxt += len(self.label.text) + 1
-    if urwid_is_mouse_press(event) and button == 1 and col > maxtxt and col <= maxtxt + len(self.DOWN_ARROW):
-      self._emit("displaycombo")
-  def displaycombo(self, src):
-    self.open_pop_up()
-  def create_pop_up(self):
-    index = self.selected_item[1]
-    popup = self.ComboSpace(self.list, index, self.combo_attrs)
-    self._overlay_left = 0
-    if self.label.text:
-      self._overlay_left = len(self.label.text)
-    (self._overlay_width, self._overlay_height) = popup.get_size()
-    connect_signal(popup, 'close', lambda x: self.close_pop_up())
-    connect_signal(popup, 'validate', self.validate_pop_up)
-    return popup
-  def get_pop_up_parameters(self):
-    return {'left':self._overlay_left, 'top':1, 'overlay_width':self._overlay_width, 'overlay_height':self._overlay_height}
-  def validate_pop_up(self, popup):
-    pos = self._pop_up_widget.selected_pos
-    text = self._item_text(self.list[pos])
-    self.close_pop_up()
-    if self._emit_change_event(text, pos):
-      self.set_selected_item(pos)
-  def _emit_change_event(self, pos, text):
-    """
-    Return True if there is no callback, or if all callback answer True
-    """
-    result = True
-    signal_obj = urwid_signals
-    d = getattr(self, signal_obj._signal_attr, {})
-    for callback, user_arg in d.get('change', []):
-      args = (self, pos, text)
-      result &= bool(callback(*args))
-    return result
-
-class ComboBoxEdit(ComboBox):
-  """
-  A ComboBox with an editable zone.
-  The combo trigger on 'enter' only, disregarding the state for self.use_enter
-  """
-  def _create_cbox_widget(self):
-    edit = EditMore(edit_text = '', wrap = CLIP)
-    connect_signal(edit, 'change', self._on_edit_change)
-    return edit
-  def keypress(self, size, key):
-    """
-    If we press enter, be a combo box!
-    """
-    if key == 'enter': # discard state of self.use_enter
-      self._emit("displaycombo")
-    else:
-      return self._original_widget.keypress(size, key)
-  def _on_edit_change(self, edit, text):
-    # prevent re-emit the change when this event has been triggered by the combo
-    if hasattr(edit, '_fromCombo') and not edit._fromCombo:
-      # we cannot prevent the edit widget from being modified, even if the combo event handlers says so
-      # so just notify about the change
-      self._emit_change_event(text, None)
-
-class TextMultiValues(SelText):
-  """
-  A selectable text that render multiple separated values, but which only display one value
-  """
-  def __init__(self, texts, selPosition = 0, join = ' | ', align = LEFT, wrap = SPACE, layout = None):
-    assert texts
-    assert isinstance(texts, list)
-    assert selPosition >= 0 and selPosition < len(texts)
-    self._texts = texts
-    self._selPosition = selPosition
-    self._join = join
-    self._fullText = join.join(texts)
-    self.__super.__init__(texts[selPosition], align, wrap, layout)
-  def set_text(self, markup):
-    self.__super.set_text(markup)
-    if hasattr(self, "texts"):
-      self._texts[self._selPosition] = self._text
-      self._updateTexts(True)
-  def getTexts(self):
-    return self._texts
-  def setTexts(self, texts):
-    self._texts = texts
-    self._updateTexts(True)
-  texts = property(getTexts, setTexts)
-  def getSelPosition(self):
-    return self._selPosition
-  def setSelPosition(self, selPosition):
-    self._selPosition = selPosition
-    self._updateTexts(False)
-  selPosition = property(getSelPosition, setSelPosition)
-  def _updateTexts(self, doFull = True):
-    if doFull:
-      self._fullText = self._join.join(self._texts)
-    self._text = self._texts[self._selPosition]
-  def render(self, size, focus = False):
-    (maxcol,) = size
-    text = self._fullText
-    attr = self.get_text()[1]
-    trans = self.get_line_translation(maxcol, (text, attr))
-    return self.canvas_with_attr(urwid_apply_text_layout(text, attr, trans, maxcol), focus)
-  def _update_cache_translation(self, maxcol, ta):
-    if ta:
-      text, attr = ta
-    else:
-      text = self._fullText
-      attr = self.get_text()[1]
-      self._cache_maxcol = maxcol
-      self._cache_translation = self._calc_line_translation(text, maxcol)
-  def pack(self, size = None, focus = False):
-    text = self._fullText
-    attr = self.get_text()[1]
-    if size is not None:
-      (maxcol,) = size
-      if not hasattr(self.layout, "pack"):
-        return size
-      trans = self.get_line_translation(maxcol, (text, attr))
-      cols = self.layout.pack(maxcol, trans)
-      return (cols, len(trans))
-    i = 0
-    cols = 0
-    while i < len(text):
-      j = text.find('\n', i)
-      if j == -1:
-        j = len(text)
-      c = calc_width(text, i, j)
-      if c > cols:
-        cols = c
-      i = j + 1
-    return (cols, text.count('\n') + 1)
-
-
-
-# This is a h4x3d copy of some of the code in Ian Ward's dialog.py example.
-class DialogExit(Exception):
-  """ Custom exception. """
-  pass
-
-class Dialog2(WidgetWrapMore):
-  """ Base class for other dialogs. """
-  def __init__(self, text, height, width, body = None):
-    self.buttons = None
-    self.width = int(width)
-    if width <= 0:
-      self.width = ('relative', 80)
-    self.height = int(height)
-    if height <= 0:
-      self.height = ('relative', 80)
-    self.body = body
-    if body is None:
-      # fill space with nothing
-      body = FillerMore(Divider(), 'top')
-    self.frame = FrameMore(body, focus_part = 'footer')
-    if text is not None:
-      self.frame.header = PileMore([
-        Text(text, align='right'),
-        Divider()
-      ])
-    w = AttrWrapMore(self.frame, 'body')
-    self.__super.__init__(w)
-  # buttons: tuple of name,exitcode
-  def add_buttons(self, buttons):
-    """ Add buttons. """
-    l = []
-    maxlen = 0
-    for name, exitcode in buttons:
-      b = ButtonMore(name, self.button_press)
-      b.exitcode = exitcode
-      b = AttrWrapMore(b, 'body', 'focus')
-      l.append(b)
-      maxlen = max(len(name), maxlen)
-    maxlen += 4  # because of '< ... >'
-    self.buttons = GridFlowMore(l, maxlen, 3, 1, 'center')
-    self.frame.footer = PileMore([
-      Divider(),
-      self.buttons
-    ], focus_item = 1)
-  def button_press(self, button):
-    """ Handle button press. """
-    raise DialogExit(button.exitcode)
-  def run(self, ui, parent):
-    """ Run the UI. """
-    ui.set_mouse_tracking()
-    size = ui.get_cols_rows()
-    overlay = OverlayMore(
-      LineBoxMore(self._w),
-      parent, 'center', self.width,
-      'middle', self.height
-    )
-    try:
-      while True:
-        canvas = overlay.render(size, focus=True)
-        ui.draw_screen(size, canvas)
-        keys = None
-        while not keys:
-          keys = ui.get_input()
-        for k in keys:
-          if urwid_is_mouse_event(k):
-            event, button, col, row = k
-            overlay.mouse_event(size, event, button, col, row, focus = True)
-          else:
-            if k == 'window resize':
-              size = ui.get_cols_rows()
-            k = self._w.keypress(size, k)
-            if k == 'esc':
-              raise DialogExit(-1)
-            if k:
-              self.unhandled_key(size, k)
-    except DialogExit, e:
-      return self.on_exit(e.args[0])
-  def on_exit(self, exitcode):
-    """ Handle dialog exit. """
-    return exitcode, ""
-  def unhandled_key(self, size, key):
-    """ Handle keypresses. """
-    pass
-
-class TextDialog(Dialog2):
-  """ Simple dialog with text and "OK" button. """
-  def __init__(self, text, height, width, header=None, align='left',
-    buttons=(_('OK'), 1)):
-    l = [Text(text)]
-    body = ListBoxMore(SimpleListWalker(l))
-    body = AttrWrapMore(body, 'body')
-    Dialog2.__init__(self, header, height + 2, width + 2, body)
-    if type(buttons) == list:
-      self.add_buttons(buttons)
-    else:
-      self.add_buttons([buttons])
-    self.frame.set_focus('footer')
-  def unhandled_key(self, size, k):
-    """ Handle keys. """
-    if k in ('up', 'page up', 'down', 'page down'):
-      self.frame.set_focus('body')
-      self._w.keypress(size, k)
-      self.frame.set_focus('footer')
-
-class InputDialog(Dialog2):
-  """ Simple dialog with text and entry. """
-  def __init__(self, text, height, width, ok_name=_('OK'), edit_text=''):
-    self.edit = EditMore(wrap='clip', edit_text=edit_text)
-    body = ListBoxMore(SimpleListWalker([self.edit]))
-    body = AttrWrapMore(body, 'editbx', 'editfc')
-    Dialog2.__init__(self, text, height, width, body)
-    self.frame.set_focus('body')
-    self.add_buttons([(ok_name, 0), (_('Cancel'), -1)])
-  def unhandled_key(self, size, k):
-    """ Handle keys. """
-    if k in ('up', 'page up'):
-      self.frame.set_focus('body')
-    if k in ('down', 'page down'):
-      self.frame.set_focus('footer')
-    if k == 'enter':
-      # pass enter to the "ok" button
-      self.frame.set_focus('footer')
-      self._w.keypress(size, k)
-  def on_exit(self, exitcode):
-    """ Handle dialog exit. """
-    return exitcode, self.edit.get_edit_text()
-
-class OptCols(WidgetWrapMore):
-  """ Htop-style menubar on the bottom of the screen. """
-  class ClickCols(WidgetWrapMore):
-    """ Clickable menubar. """
-    def __init__(self, keyText, desc, attrKey, attrDesc, callback = None, keys = None):
-      items = [('fixed', len(keyText) + 1, Text((attrKey, keyText))), Text((attrDesc, desc))]
-      cols = ColumnsMore(items)
-      cols.attr = attrDesc
-      self.__super.__init__(cols)
-      self.callback = callback
-      self.keys = keys
-
-    def mouse_event(self, size, event, button, x, y, focus):
-      if event == "mouse press" and self.keys and len(self.keys):
-        self.callback(self.keys[0]) # the first key is used
-  # tuples = [((key1, key2, …), desc)], on_event gets passed a key
-  # handler = function passed the key of the "button" pressed
-  # attrs = (attr_key, attr_desc)
-  # mentions of 'left' and right will be converted to <- and -> respectively
-  def __init__(self, tuples, handler, attrs = ('body', 'infobar')):
-    # Construct the texts
-    textList = []
-    # callbacks map the text contents to its assigned callback.
-    self.callbacks = []
-    for cmd in tuples:
-      keys = cmd[0]
-      if type(keys) != tuple:
-        keys = (keys,)
-      newKeys = {}
-      for key in keys:
-        newkey = reduce(lambda s, (f, t): s.replace(f, t), [
-          ('ctrl ', 'Ctrl+'),
-          ('meta ', 'Alt+'),
-          ('left', '←'),
-          ('right', '→'),
-          ('up', '↑'),
-          ('down', '↓'),
-          ('page up', 'Page Up'),
-          ('page down', 'Page Down'),
-          ('esc', 'ESC'),
-          ('enter', 'Enter')],
-          key)
-        if re.match(r"^[a-z]([0-9]*)$", newkey):
-          newkey = newkey.upper()
-        elif re.match(r".*\+.*", newkey):
-          newkey = newkey.split("+")
-          newkey = newkey[0] + "+" + newkey[1].upper()
-        newKeys[key] = newkey
-      desc = cmd[1]
-      keyText = " / ".join([newKeys[key] for key in keys]) + ":"
-      col = self.ClickCols(keyText, desc, attrs[0], attrs[1], handler, keys)
-      textList.append(col)
-    cols = ColumnsMore(textList)
-    WidgetWrapMore.__init__(self, cols)
-
-  def mouse_event(self, size, event, button, x, y, focus):
-    """ Handle mouse events. """
-    # Widgets are evenly long (as of current), so...
-    return self._w.mouse_event(size, event, button, x, y, focus)
diff --git a/src/tests/combobox.py b/src/tests/combobox.py
deleted file mode 100755 (executable)
index ec1bb7c..0000000
+++ /dev/null
@@ -1,117 +0,0 @@
-#!/usr/bin/python
-# coding: utf-8
-
-import gettext
-gettext.install('combobox')
-import sys
-sys.path.append('../lib/')
-import urwid_more as urwidm
-
-def pause():
-  raw_input("pause")
-
-class ComboBoxEditAdd(urwidm.ComboBoxEdit):
-  def keypress(self, size, key):
-    if key == '+':
-      (item, pos) = self.get_selected_item()
-      if pos is None:
-        self.list.append(item)
-    else:
-      return self.__super.keypress(size, key)
-
-def main_event(input):
-  if input in ('q', 'Q', 'f10'):
-    raise urwidm.ExitMainLoop
-
-l1 = [
-  u"val1",
-  u"val2",
-  u"val3",
-]
-class ComplexWidget(urwidm.WidgetWrapMore):
-  def __init__(self, left = u'', center = u'', right = u''):
-    w = urwidm.ColumnsMore([
-      ('fixed', len(left), urwidm.Text(left)),
-      ('fixed', len(center), urwidm.Text(center)),
-      ('fixed', len(right), urwidm.Text(right))
-    ])
-    self.__super.__init__(w)
-  def get_text(self):
-    return self._w.widget_list[1].text.upper()
-  def set_text(self, text):
-    self._w.widget_list[1].set_text(text)
-  text = property(get_text)
-  def set_sensitive_attr(self, attr):
-    if type(attr) != tuple:
-      attr = (attr, attr)
-    self._sensitive_attr = attr
-    try:
-      if hasattr(self._w, 'sensitive_attr'):
-        self._w.sensitive_attr = attr
-      for w in self._w.widget_list:
-        if hasattr(w, 'sensitive_attr'):
-          w.sensitive_attr = attr
-    except:
-      pass
-  def set_unsensitive_attr(self, attr):
-    if type(attr) != tuple:
-      attr = (attr, attr)
-    self._unsensitive_attr = attr
-    try:
-      if hasattr(self._w, 'unsensitive_attr'):
-        wself._.unsensitive_attr = attr
-      for w in self._w.widget_list:
-        if hasattr(w, 'unsensitive_attr'):
-          w.unsensitive_attr = attr
-    except:
-      pass
-  def keypress(self, size, key):
-    return key
-  def selectable(self):
-    return True
-  def pack(self, size = None, focus = False):
-    if size is None:
-      w = 0
-      for sw in self._w.widget_list:
-        w += len(sw.text)
-      return (w, 1)
-    else:
-      self.__super.pack(size, focus)
-
-l2 = [
-  urwidm.SelText(u"prop1"),
-  ComplexWidget(u"«left,", u"prop2", u",right»"),
-  urwidm.TextMultiValues([u"text", u"multi", u"values"], selPosition = 1),
-]
-fill = urwidm.Filler(urwidm.PileMore([
-  urwidm.Padding(urwidm.Text(u"ComboBox tests"), 'center'),
-  urwidm.Divider(),
-  urwidm.Padding(urwidm.ComboBox(items = l1), 'center', 10),
-  urwidm.Divider(),
-  urwidm.Padding(urwidm.Text(u"Use + to add an item to the list"), 'center'),
-  urwidm.Padding(ComboBoxEditAdd(label = u"props:", items = l2, focus_index = 1), 'center', 20),
-  urwidm.Divider(),
-  urwidm.Padding(urwidm.Text(u"Q or F10 to quit"), 'center'),
-]))
-loop = urwidm.MainLoop(
-    fill,
-    [
-      ('body', 'light gray', 'black'),
-      ('header', 'dark blue', 'light gray'),
-      ('footer', 'light green', 'black', 'bold'),
-      ('footer_key', 'yellow', 'black', 'bold'),
-      ('strong', 'white', 'black', 'bold'),
-      ('focusable', 'light green', 'black'),
-      ('unfocusable', 'brown', 'black'),
-      ('focus', 'black', 'light green'),
-      ('focus_edit', 'yellow', 'black'),
-      ('focus_icon', 'yellow', 'black'),
-      ('focus_radio', 'yellow', 'black'),
-      ('comboitem', 'dark blue', 'dark cyan'),
-      ('comboitem_focus', 'black', 'brown'),
-      ('error', 'white', 'light red'),
-      ('focus_error', 'light red', 'black'),
-    ],
-    pop_ups=True,
-    unhandled_input=main_event)
-loop.run()
diff --git a/tox.ini b/tox.ini
new file mode 100644 (file)
index 0000000..0dff541
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,3 @@
+[flake8]
+ignore = E111,E121,F403
+max-line-length = 512