[SCM] GUI front-end for Debian Live. branch, master, updated. 11e67a56b24563c3f7a488602604f9ba897740c3
Chris Lamb
chris at chris-lamb.co.uk
Tue Mar 4 03:01:19 UTC 2008
The following commit has been merged in the master branch:
commit f62a975316863ae347e34ff906321d762da756e5
Author: Chris Lamb <chris at chris-lamb.co.uk>
Date: Tue Mar 4 03:00:11 2008 +0000
Rewrite of configuration API.
Signed-off-by: Chris Lamb <chris at chris-lamb.co.uk>
diff --git a/DebianLive/__init__.py b/DebianLive/__init__.py
new file mode 100644
index 0000000..e63f47e
--- /dev/null
+++ b/DebianLive/__init__.py
@@ -0,0 +1,36 @@
+from DebianLive import utils
+
+import os
+import commands
+
+class Config(object):
+ def __init__(self, dir, spec=None):
+ self.dir = dir
+
+ if spec is None:
+ # Load default field specification
+ from spec import spec
+
+ # Create skeleton lh_config dir, if it does not already exist
+ if not os.path.exists(os.path.join(self.dir, 'config')):
+ if not os.path.exists(self.dir):
+ os.makedirs(self.dir)
+ cmd = 'cd "%s"; lh_config' % os.path.abspath(self.dir)
+ result, out = commands.getstatusoutput(cmd)
+ if result != 0:
+ raise IOError, out
+
+ self.children = {}
+ for name, details in spec.iteritems():
+ elem_type = details[0]
+ elem = elem_type(self.dir, name, *details[1:])
+ self.children[name] = elem
+ setattr(self, name, elem)
+
+ def __str__(self):
+ from pprint import pformat
+ return '<DebianLive.Config dir="%s" %s>' % (self.dir, pformat(self.children))
+
+ def save(self):
+ for elem in self.children.values():
+ elem.save()
diff --git a/DebianLive/elements/__init__.py b/DebianLive/elements/__init__.py
new file mode 100644
index 0000000..5ce3bf7
--- /dev/null
+++ b/DebianLive/elements/__init__.py
@@ -0,0 +1,2 @@
+from folder_of_files import FolderOfFiles
+from key_var import KeyVar
diff --git a/DebianLive/elements/folder_of_files.py b/DebianLive/elements/folder_of_files.py
new file mode 100644
index 0000000..0addf98
--- /dev/null
+++ b/DebianLive/elements/folder_of_files.py
@@ -0,0 +1,123 @@
+import glob
+import os
+
+from os.path import join
+
+class FolderOfFiles(object):
+ """
+ Represents a folder containing a number of files.
+ """
+
+ def __init__(self, basedir, name, dir):
+ self.dir = os.path.join(basedir, 'config', dir)
+
+ self._stale = set()
+ self.files = {}
+
+ # No file has been deleted
+ self.file_deleted = False
+
+ self.load()
+
+ def __getitem__(self, k):
+ return self.files[k]
+
+ def __contains__(self, k):
+ return k in self.files
+
+ def __delitem__(self, k):
+ del self.files[k]
+
+ def __setitem__(self, k, v):
+ self._stale.add(k)
+ self.files[k] = v
+
+ def _config_exists(self, file):
+ try:
+ os.stat(join(self.dir, file))
+ return True
+ except OSError:
+ return False
+
+ def load(self):
+ """
+ Loads files.
+ """
+ self.deleted = False
+ self._stale.clear()
+ self.files.clear()
+ for name in glob.glob(join(self.dir, '*')):
+ key = name.split('/')[-1]
+ try:
+ f = open(name, 'r')
+ self.files[key] = f.read()
+ f.close()
+ except IOError, e:
+ # "Is a directory"
+ if e.errno == 21:
+ continue
+ raise e
+
+ def save(self):
+ """
+ Update all updated files in this directory.
+ """
+ for filename in self._stale:
+ pathname = join(self.dir, filename)
+ f = open(pathname, 'w+')
+ f.write(self[filename])
+ f.close()
+
+ self._stale.clear()
+
+ def delete(self, hook_name):
+ if self._config_exists(hook_name):
+ os.remove(join(self.dir, hook_name))
+ del self[hook_name]
+ if hook_name in self._stale: self._stale.remove(hook_name)
+ self.file_deleted = True
+
+ def rename(self, orig, new):
+ """
+ Throws ValueError if 'new' already exists.
+ """
+ if self._config_exists(new):
+ raise ValueError
+ if self._config_exists(orig):
+ os.rename(join(self.dir, orig), join(self.dir, new))
+ if orig in self._stale: self._stale.remove(orig)
+ self[new] = self[orig]
+ del self[orig]
+
+ def import_file(self, source):
+ """
+ Imports the specified file into the current configuration, using a
+ unique name. The file is not saved.
+ """
+ f = open(source, 'r')
+ source_contents = f.read()
+ f.close()
+
+ target_name = self._gen_import_name(source)
+ self[target_name] = source_contents
+
+ return target_name
+
+ def _gen_import_name(self, filename):
+ """
+ Generates a unique name of the imported file.
+ """
+ # Use existing filename as the root
+ root = filename.split(os.sep)[-1]
+
+ if root in self:
+ # Keep adding a number to the end until it doesn't exist.
+ i = 1
+ while True:
+ tmpnam = "%s-%d" % (root, i)
+ if not tmpnam in self:
+ return tmpnam
+ i += 1
+ else:
+ # Just use the root name
+ return root
diff --git a/DebianLive/elements/key_var.py b/DebianLive/elements/key_var.py
new file mode 100644
index 0000000..8c5e55e
--- /dev/null
+++ b/DebianLive/elements/key_var.py
@@ -0,0 +1,137 @@
+import re
+import os
+
+from DebianLive.utils import ListObserver
+
+SHELL_ESCAPES = (
+ (r'\ '[:-1], r'\\ '[:-1]),
+ (r'"', r'\"'),
+ (r'`', r'\`'),
+ (r'$', r'\$'),
+ (r"'", r'\''),
+)
+
+REGEX = re.compile(r"""^\s*(\w+)=(?:(["\'])(([^\\\2]|\\.)*|)\2|((\w|\\["'])*))\s*(?:#.*)?$""")
+
+"""
+TODO
+ - Test case where:
+ >>> my_key_var = KeyVar('.', None, {'spam': list})
+ >>> print my_key_var['spam']
+ []
+ >>> print my_key_var['stale']
+ set([])
+ >>> print my_key_var['spam'] = ['spam']
+ >>> print my_key_var.stale
+ set(['spam'])
+ >>> my_key_var.save()
+ >>> print my_key_var.stale
+ set([])
+ >>> my_key_var['spam'].append('eggs')
+ >>> print my_key_var.stale
+ set([]) <-- Should be 'spam'
+"""
+
+class KeyVar(dict):
+ '''
+ Represents a POSIX shell KEY="VAR" configuration file.
+ '''
+
+ def __new__(cls, *args, **kwargs):
+ return dict.__new__(cls, *args, **kwargs)
+
+ def __init__(self, dir, name, spec):
+ self.filename = os.path.join(dir, 'config', name)
+
+ self.line_numbers = {}
+ self.stale = set()
+
+ f = open(self.filename, 'r')
+ try:
+ line_no = 1
+ for line in f:
+
+ # Check and parse key=value lines
+ match = REGEX.match(line)
+ if not match:
+ continue
+
+ key = match.groups()[0]
+
+ # Find the correct match group
+ for m in match.groups()[2:]:
+ if m is not None:
+ val = m
+ break
+
+ # Unescape value
+ for to, from_ in SHELL_ESCAPES:
+ val = val.replace(from_, to)
+
+ # Save line number
+ self.line_numbers[key] = line_no
+
+ # Mutate to file type
+ val_type = spec.get(key, str)
+ typed_val = {
+ int: lambda k, v: {'': None}.get(v, None),
+ list: lambda k, v: ListObserver(v.split(), lambda: self.stale.add(k)),
+ str: lambda k, v: v,
+ bool: lambda k, v: {'enabled' : True, 'disabled' : False, 'yes' : True, 'no' : False}.get(v, None),
+ }[val_type](key, val)
+
+ # Save value
+ dict.__setitem__(self, key, typed_val)
+
+ line_no += 1
+ finally:
+ f.close()
+
+ def __setitem__(self, key, value):
+ self.stale.add(key)
+ dict.__setitem__(self, key, value)
+
+ def save(self):
+ """
+ Update all updated entries in the file.
+ """
+ if len(self.stale) == 0:
+ return
+
+ f = open(self.filename, 'r+')
+ lines = f.readlines()
+
+ for k in self.stale:
+ val = self[k]
+
+ # Escape value
+ if type(val) in (list, ListObserver):
+ for from_, to in SHELL_ESCAPES:
+ val = map(lambda x: x.replace(from_, to), val)
+ val = map(str.strip, val)
+ elif type(val) is str:
+ for from_, to in SHELL_ESCAPES:
+ val = val.replace(from_, to)
+
+ # Format value depending on its type
+ line_value = {
+ list : lambda v: " ".join(val),
+ bool : lambda v: {True: 'enabled', False: 'disabled'}.get(val, None),
+ str : lambda v: v,
+ type(None) : lambda v: "",
+ }[type(val)](val)
+
+ line = '%s="%s"\n' % (k, line_value)
+
+ try:
+ # Overwrite original line in file
+ lines[self.line_numbers[k] - 1] = line
+ except KeyError:
+ # Append line to end of file
+ lines.append("\n# The following option was added by live-magic\n")
+ lines.append(line)
+ f.close()
+
+ f = open(self.filename, 'w')
+ f.writelines(lines)
+ f.close()
diff --git a/DebianLive/spec.py b/DebianLive/spec.py
new file mode 100644
index 0000000..7139963
--- /dev/null
+++ b/DebianLive/spec.py
@@ -0,0 +1,116 @@
+from DebianLive.elements import FolderOfFiles
+from DebianLive.elements import KeyVar
+
+spec = {
+ 'chroot_hooks': (FolderOfFiles, 'chroot_local-hooks'),
+ 'binary_hooks': (FolderOfFiles, 'binary_local-hooks'),
+
+ 'binary': (KeyVar, {
+ 'LH_BINARY_FILESYSTEM': str,
+ 'LH_BINARY_IMAGES': list,
+ 'LH_BINARY_INDICES': bool,
+ 'LH_BOOTAPPEND_LIVE': str,
+ 'LH_BOOTAPPEND_INSTALL': str,
+ 'LH_BOOTLOADER': str,
+ 'LH_CHECKSUMS': bool,
+ 'LH_CHROOT_BUILD': bool,
+ 'LH_DEBIAN_INSTALLER': bool,
+ 'LH_DEBIAN_INSTALLER_DAILY': bool,
+ 'LH_ENCRYPTION': str,
+ 'LH_GRUB_SPLASH': str,
+ 'LH_HOSTNAME': str,
+ 'LH_ISO_APPLICATION': str,
+ 'LH_ISO_PREPARER': str,
+ 'LH_ISO_PUBLISHER': str,
+ 'LH_ISO_VOLUME': str,
+ 'LH_JFFS2_ERASEBLOCK': str,
+ 'LH_MEMTEST': str,
+ 'LH_NET_ROOT_FILESYSTEM': str,
+ 'LH_NET_ROOT_MOUNTOPTIONS': str,
+ 'LH_NET_ROOT_PATH': str,
+ 'LH_NET_ROOT_SERVER': str,
+ 'LH_NET_COW_FILESYSTEM': str,
+ 'LH_NET_COW_MOUNTOPTIONS': str,
+ 'LH_NET_COW_PATH': str,
+ 'LH_NET_COW_SERVER': str,
+ 'LH_NET_TARBALL': str,
+ 'LH_SYSLINUX_SPLASH': str,
+ 'LH_SYSLINUX_TIMEOUT': int,
+ 'LH_SYSLINUX_CFG': str,
+ 'LH_SYSLINUX_MENU': bool,
+ 'LH_SYSLINUX_MENU_LIVE_ENTRY': str,
+ 'LH_SYSLINUX_MENU_LIVE_FAILSAFE_ENTRY': str,
+ 'LH_SYSLINUX_MENU_MEMTEST_ENTRY': str,
+ 'LH_USERNAME': str,
+ }),
+
+ 'bootstrap': (KeyVar, {
+ 'LH_ARCHITECTURE': str,
+ 'LH_BOOTSTRAP_CONFIG': str,
+ 'LH_BOOTSTRAP_INCLUDE': str,
+ 'LH_BOOTSTRAP_EXCLUDE': str,
+ 'LH_BOOTSTRAP_FLAVOUR': str,
+ 'LH_BOOTSTRAP_KEYRING': str,
+ 'LH_DISTRIBUTION': str,
+ 'LH_MIRROR_BOOTSTRAP': str,
+ 'LH_MIRROR_CHROOT': str,
+ 'LH_MIRROR_CHROOT_SECURITY': str,
+ 'LH_MIRROR_BINARY': str,
+ 'LH_MIRROR_BINARY_SECURITY': str,
+ 'LH_SECTIONS': list,
+ }),
+
+ 'chroot': (KeyVar, {
+ 'LH_CHROOT_FILESYSTEM': str,
+ 'LH_UNION_FILESYSTEM': str,
+ 'LH_EXPOSED_ROOT': bool,
+ 'LH_HOOKS': list,
+ 'LH_INTERACTIVE': bool,
+ 'LH_KEYRING_PACKAGES': list,
+ 'LH_LANGUAGE': str,
+ 'LH_LINUX_FLAVOURS': list,
+ 'LH_LINUX_PACKAGES': list,
+ 'LH_PACKAGES': list,
+ 'LH_PACKAGES_LISTS': str,
+ 'LH_TASKS': str,
+ 'LH_SECURITY': bool,
+ 'LH_SYMLINKS': bool,
+ 'LH_SYSVINIT': bool,
+ }),
+
+ 'common': (KeyVar, {
+ 'LH_APT': str,
+ 'LH_APT_FTP_PROXY': str,
+ 'LH_APT_HTTP_PROXY': str,
+ 'LH_APT_PDIFFS': bool,
+ 'LH_APT_PIPELINE': int,
+ 'LH_APT_RECOMMENDS': bool,
+ 'LH_APT_SECURE': bool,
+ 'LH_BOOTSTRAP': str,
+ 'LH_CACHE': bool,
+ 'LH_CACHE_INDICES': bool,
+ 'LH_CACHE_STAGES': list,
+ 'LH_DEBCONF_FRONTEND': str,
+ 'LH_DEBCONF_NOWARNINGS': bool,
+ 'LH_DEBCONF_PRIORITY': str,
+ 'LH_INITRAMFS': str,
+ 'LH_FDISK': str,
+ 'LH_LOSETUP': str,
+ 'LH_MODE': str,
+ 'LH_USE_FAKEROOT': str,
+ 'LH_TASKSEL': str,
+ 'LH_INCLUDES': str,
+ 'LH_TEMPLATES': str,
+
+ 'LH_BREAKPOINTS': bool,
+ 'LH_DEBUG': bool,
+ 'LH_FORCE': bool,
+ 'LH_VERBOSE': bool,
+ 'LH_QUIET': bool,
+ }),
+
+ 'source': (KeyVar, {
+ 'LH_SOURCE': bool,
+ 'LH_SOURCE_IMAGES': list,
+ }),
+}
diff --git a/DebianLive/utils/__init__.py b/DebianLive/utils/__init__.py
new file mode 100644
index 0000000..764dfe2
--- /dev/null
+++ b/DebianLive/utils/__init__.py
@@ -0,0 +1,2 @@
+from sources_list import SourcesList
+from list_observer import ListObserver
diff --git a/DebianLive/utils/list_observer.py b/DebianLive/utils/list_observer.py
new file mode 100644
index 0000000..4be8771
--- /dev/null
+++ b/DebianLive/utils/list_observer.py
@@ -0,0 +1,68 @@
+class ListObserver(list):
+ """
+ Observed list implementation.
+
+ Calls fn_observed whenever you alter the list.
+
+ >>> my_list = ['spam', 'eggs']
+ >>> def fn(): print "Observed."
+ >>> my_list = list_observer(my_list, fn)
+ >>> my_list.append('bacon')
+ Observed.
+ >>> my_list.pop()
+ Observed.
+ bacon
+ >>>
+
+ """
+
+ def __init__(self, value, fn_observed):
+ list.__init__(self, value)
+ self.fn_observed = fn_observed
+
+ def __iter__(self):
+ return list.__iter__(self)
+
+ def __setitem__(self,key,value):
+ list.__setitem__(self, key, value)
+ self.fn_observed()
+
+ def __delitem__(self,key):
+ list.__delitem__(self, key)
+ self.fn_observed()
+
+ def __setslice__(self, i, j, sequence):
+ list.__setslice__(self, i, j, sequence)
+ self.fn_observed()
+
+ def __delslice__(self, i, j):
+ list.__delslice__(self, i, j)
+ self.fn_observed()
+
+ def append(self, value):
+ list.append(self, value)
+ self.fn_observed()
+
+ def pop(self):
+ self.fn_observed()
+ return list.pop(self)
+
+ def extend(self, newvalue):
+ self.fn_observed()
+ list.extend(self, newvalue)
+
+ def insert(self, i, element):
+ list.insert(self, i, element)
+ self.fn_observed()
+
+ def remove(self, element):
+ list.remove(self, element)
+ self.fn_observed()
+
+ def reverse(self):
+ list.reverse(self)
+ self.fn_observed()
+
+ def sort(self, cmpfunc=None):
+ list.sort(self, cmpfunc)
+ self.fn_observed()
--
GUI front-end for Debian Live.
More information about the debian-live-changes
mailing list