From e4293652d5b2a80725e6f83843f91364e19ba199 Mon Sep 17 00:00:00 2001 From: Gunnar Wrobel Date: Tue, 11 Sep 2007 05:53:25 +0000 Subject: Import layman. --- .svn.ignore | 1 + __init__.py | 1 + action.py | 420 ++++++++++++++++++++++++++ config.py | 300 ++++++++++++++++++ db.py | 585 ++++++++++++++++++++++++++++++++++++ debug.py | 501 ++++++++++++++++++++++++++++++ overlay.py | 247 +++++++++++++++ overlays/.svn.ignore | 1 + overlays/__init__.py | 1 + overlays/bzr.py | 66 ++++ overlays/cvs.py | 73 +++++ overlays/darcs.py | 64 ++++ overlays/git.py | 63 ++++ overlays/mercurial.py | 64 ++++ overlays/overlay.py | 266 ++++++++++++++++ overlays/rsync.py | 68 +++++ overlays/svn.py | 65 ++++ overlays/tar.py | 189 ++++++++++++ tests/dtest.py | 90 ++++++ tests/testfiles/global-overlays.xml | 30 ++ tests/testfiles/layman-test.tar.bz2 | Bin 0 -> 845 bytes tests/testfiles/make.conf | 345 +++++++++++++++++++++ utils.py | 208 +++++++++++++ version.py | 24 ++ 24 files changed, 3672 insertions(+) create mode 100644 .svn.ignore create mode 100644 __init__.py create mode 100644 action.py create mode 100644 config.py create mode 100644 db.py create mode 100644 debug.py create mode 100644 overlay.py create mode 100644 overlays/.svn.ignore create mode 100644 overlays/__init__.py create mode 100644 overlays/bzr.py create mode 100644 overlays/cvs.py create mode 100644 overlays/darcs.py create mode 100644 overlays/git.py create mode 100644 overlays/mercurial.py create mode 100644 overlays/overlay.py create mode 100644 overlays/rsync.py create mode 100644 overlays/svn.py create mode 100644 overlays/tar.py create mode 100644 tests/dtest.py create mode 100644 tests/testfiles/global-overlays.xml create mode 100644 tests/testfiles/layman-test.tar.bz2 create mode 100644 tests/testfiles/make.conf create mode 100644 utils.py create mode 100644 version.py diff --git a/.svn.ignore b/.svn.ignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.svn.ignore @@ -0,0 +1 @@ +*.pyc diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +# diff --git a/action.py b/action.py new file mode 100644 index 0000000..b2c67ce --- /dev/null +++ b/action.py @@ -0,0 +1,420 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN ACTIONS +################################################################################# +# File: action.py +# +# Handles layman actions. +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# +''' Provides the different actions that can be performed by layman.''' + +__version__ = "$Id: action.py 312 2007-04-09 19:45:49Z wrobel $" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +import sys + +from layman.db import DB, RemoteDB + +from layman.debug import OUT + +#=============================================================================== +# +# Class Fetch +# +#------------------------------------------------------------------------------- + +class Fetch: + ''' Fetches the overlay listing. + + >>> import os + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> cache = os.tmpnam() + >>> config = {'overlays' : + ... 'file://' + here + '/tests/testfiles/global-overlays.xml', + ... 'cache' : cache, + ... 'nocheck' : True, + ... 'proxy' : None, + ... 'quietness':3} + >>> a = Fetch(config) + >>> a.run() + 0 + >>> b = open(a.db.path(config['overlays'])) + >>> b.readlines()[24] + ' A collection of ebuilds from Gunnar Wrobel [wrobel@gentoo.org].\\n' + + >>> b.close() + >>> os.unlink(a.db.path(config['overlays'])) + + >>> a.db.overlays.keys() + [u'wrobel', u'wrobel-stable'] + ''' + + def __init__(self, config): + self.db = RemoteDB(config) + + def run(self): + '''Fetch the overlay listing.''' + try: + self.db.cache() + except Exception, error: + OUT.die('Failed to fetch overlay list!\nError was: ' + + str(error)) + + return 0 + +#=============================================================================== +# +# Class Sync +# +#------------------------------------------------------------------------------- + +class Sync: + ''' Syncs the selected overlays.''' + + def __init__(self, config): + + self.db = DB(config) + + self.rdb = RemoteDB(config) + + self.selection = config['sync'] + + if config['sync_all'] or 'ALL' in self.selection: + self.selection = self.db.overlays.keys() + + def run(self): + '''Synchronize the overlays.''' + + OUT.debug('Updating selected overlays', 6) + + warnings = [] + success = [] + for i in self.selection: + ordb = self.rdb.select(i) + odb = self.db.select(i) + if ordb and odb and ordb.src != odb.src: + warnings.append( + 'The source of the overlay "' + i + '" seems to have c' + 'hanged. You currently sync from "' + odb.src + '" whi' + 'le the global layman list reports "' + ordb.src + '" ' + 'as correct location. Please consider removing and rea' + 'dding the overlay!') + + try: + self.db.sync(i) + success.append('Successfully synchronized overlay "' + i + '".') + except Exception, error: + warnings.append( + 'Failed to sync overlay "' + i + '".\nError was: ' + + str(error)) + + if success: + OUT.info('\nSuccess:\n------\n', 3) + for i in success: + OUT.info(i, 3) + + if warnings: + OUT.warn('\nErrors:\n------\n', 2) + for i in warnings: + OUT.warn(i + '\n', 2) + return 1 + + return 0 + +#=============================================================================== +# +# Class Add +# +#------------------------------------------------------------------------------- + +class Add: + ''' Adds the selected overlays.''' + + def __init__(self, config): + + self.config = config + + self.db = DB(config) + + self.rdb = RemoteDB(config) + + self.selection = config['add'] + + if 'ALL' in self.selection: + self.selection = self.rdb.overlays.keys() + + def run(self): + '''Add the overlay.''' + + OUT.debug('Adding selected overlays', 6) + + result = 0 + + for i in self.selection: + overlay = self.rdb.select(i) + + OUT.debug('Selected overlay', 7) + + if overlay: + try: + self.db.add(overlay) + OUT.info('Successfully added overlay "' + i + '".', 2) + except Exception, error: + OUT.warn('Failed to add overlay "' + i + '".\nError was: ' + + str(error), 2) + result = 1 + else: + OUT.warn('Overlay "' + i + '" does not exist!', 2) + result = 1 + + return result + +#=============================================================================== +# +# Class Delete +# +#------------------------------------------------------------------------------- + +class Delete: + ''' Deletes the selected overlays.''' + + def __init__(self, config): + + self.db = DB(config) + + self.selection = config['delete'] + + if 'ALL' in self.selection: + self.selection = self.db.overlays.keys() + + def run(self): + '''Delete the overlay.''' + + OUT.debug('Deleting selected overlays', 6) + + result = 0 + + for i in self.selection: + overlay = self.db.select(i) + + OUT.debug('Selected overlay', 7) + + if overlay: + try: + self.db.delete(overlay) + OUT.info('Successfully deleted overlay "' + i + '".', 2) + except Exception, error: + OUT.warn('Failed to delete overlay "' + i + '".\nError was: ' + + str(error), 2) + result = 1 + else: + OUT.warn('Overlay "' + i + '" does not exist!', 2) + result = 1 + + return result + +#=============================================================================== +# +# Class List +# +#------------------------------------------------------------------------------- + +class List: + ''' Lists the available overlays. + + >>> import os + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> cache = os.tmpnam() + >>> config = {'overlays' : + ... 'file://' + here + '/tests/testfiles/global-overlays.xml', + ... 'cache' : cache, + ... 'proxy' : None, + ... 'nocheck' : False, + ... 'verbose': False, + ... 'quietness':3} + >>> a = List(config) + >>> a.rdb.cache() + >>> OUT.color_off() + >>> a.run() + * wrobel [Subversion] (source: https://overlays.gentoo.or...) + 0 + >>> a.config['verbose'] = True + >>> a.run() + * wrobel + * ~~~~~~ + * Source : https://overlays.gentoo.org/svn/dev/wrobel + * Contact : nobody@gentoo.org + * Type : Subversion; Priority: 10 + * + * Description: + * Test + * + * *** This is no official gentoo overlay *** + * + * wrobel-stable + * ~~~~~~~~~~~~~ + * Source : rsync://gunnarwrobel.de/wrobel-stable + * Contact : nobody@gentoo.org + * Type : Rsync; Priority: 50 + * + * Description: + * A collection of ebuilds from Gunnar Wrobel [wrobel@gentoo.org]. + * + 0 + ''' + + def __init__(self, config): + + OUT.debug('Creating RemoteDB handler', 6) + + self.rdb = RemoteDB(config) + self.config = config + + def run(self): + ''' List the available overlays.''' + + for i in self.rdb.list(self.config['verbose']): + # Is the overlay supported? + if i[1]: + # Is this an official overlay? + if i[2]: + OUT.info(i[0], 1) + # Unofficial overlays will only be listed if we are not + # checking or listing verbose + elif self.config['nocheck'] or self.config['verbose']: + # Give a reason why this is marked yellow if it is a verbose + # listing + if self.config['verbose']: + OUT.warn('*** This is no official gentoo overlay ***\n', 1) + OUT.warn(i[0], 1) + # Unsupported overlays will only be listed if we are not checking + # or listing verbose + elif self.config['nocheck'] or self.config['verbose']: + # Give a reason why this is marked red if it is a verbose + # listing + if self.config['verbose']: + OUT.error('*** You are lacking the necessary tools to insta' + 'll this overlay ***\n') + OUT.error(i[0]) + + return 0 + +#=============================================================================== +# +# Class ListLocal +# +#------------------------------------------------------------------------------- + +class ListLocal: + ''' Lists the local overlays.''' + + def __init__(self, config): + self.db = DB(config) + self.config = config + + def run(self): + '''List the overlays.''' + + for i in self.db.list(self.config['verbose']): + + OUT.debug('Printing local overlay.', 8) + + # Is the overlay supported? + if i[1]: + # Is this an official overlay? + if i[2]: + OUT.info(i[0], 1) + # Unofficial overlays will only be listed if we are not + # checking or listing verbose + else: + # Give a reason why this is marked yellow if it is a verbose + # listing + if self.config['verbose']: + OUT.warn('*** This is no official gentoo overlay ***\n', 1) + OUT.warn(i[0], 1) + # Unsupported overlays will only be listed if we are not checking + # or listing verbose + else: + # Give a reason why this is marked red if it is a verbose + # listing + if self.config['verbose']: + OUT.error('*** You are lacking the necessary tools to insta' + 'll this overlay ***\n') + OUT.error(i[0]) + + return 0 + +#=============================================================================== +# +# Class Actions +# +#------------------------------------------------------------------------------- + +class Actions: + '''Dispatches to the actions the user selected. ''' + + # Given in order of precedence + actions = [('fetch', Fetch), + ('add', Add), + ('sync', Sync), + ('sync_all', Sync), + ('delete', Delete), + ('list', List), + ('list_local', ListLocal),] + + def __init__(self, config): + + # Make fetching the overlay list a default action + if not 'nofetch' in config.keys(): + # Actions that implicitely call the fetch operation before + fetch_actions = ['fetch', 'sync', 'sync_all', 'list'] + for i in fetch_actions: + if i in config.keys(): + # Implicitely call fetch, break loop + Fetch(config).run() + break + + result = 0 + + for i in self.actions: + + OUT.debug('Checking for action', 7) + + if i[0] in config.keys(): + result += i[1](config).run() + + + if not result: + sys.exit(0) + else: + sys.exit(1) + +#=============================================================================== +# +# Testing +# +#------------------------------------------------------------------------------- + +if __name__ == '__main__': + import doctest, sys + + # Ignore warnings here. We are just testing + from warnings import filterwarnings, resetwarnings + filterwarnings('ignore') + + doctest.testmod(sys.modules[__name__]) + + resetwarnings() diff --git a/config.py b/config.py new file mode 100644 index 0000000..287f223 --- /dev/null +++ b/config.py @@ -0,0 +1,300 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN CONFIGURATION +################################################################################# +# File: config.py +# +# Handles layman configuration +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# +'''Defines the configuration options and provides parsing functionality.''' + +__version__ = "$Id: config.py 286 2007-01-09 17:48:23Z wrobel $" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +import sys, ConfigParser + +from optparse import OptionParser, OptionGroup +from layman.debug import OUT +from layman.version import VERSION + +#=============================================================================== +# +# Class Config +# +#------------------------------------------------------------------------------- + +class Config(object): + '''Handles the configuration.''' + + def __init__(self): + ''' + Creates and describes all possible polymeraZe options and creates + a debugging object. + + >>> import os.path + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> sys.argv.append('--config') + >>> sys.argv.append(here + '/../etc/layman.cfg') + >>> a = Config() + >>> a['overlays'] + '\\nhttp://www.gentoo.org/proj/en/overlays/layman-global.txt' + >>> sorted(a.keys()) + ['cache', 'config', 'config_dir', 'local_list', 'make_conf', 'nocheck', 'overlays', 'proxy', 'quietness', 'storage'] + ''' + + self.defaults = {'config_dir': '/etc/layman', + 'config' : '/etc/layman/layman.cfg', + 'storage' : '/usr/portage/local/layman', + 'cache' : '%(storage)s/cache', + 'local_list': '%(storage)s/overlays.xml', + 'make_conf' : '%(storage)s/make.conf', + 'nocheck' : 'no', + 'proxy' : '', + 'overlays' : + 'http://www.gentoo.org/proj/en/overlays/layman-global.' + 'txt',} + + + self.parser = OptionParser( + usage = '\n\nlayman -a/-d/-S|overlay\nlayman -f [-o url]\nlayman' \ + ' -l|-L', + version = VERSION) + + #----------------------------------------------------------------- + # Main Options + + group = OptionGroup(self.parser, + '') + + group.add_option('-a', + '--add', + action = 'append', + help = 'Add the given overlay from the cached remote li' + 'st to your locally installed overlays.. Specify "ALL" ' + 'to add all overlays from the remote list.') + + group.add_option('-d', + '--delete', + action = 'append', + help = 'Remove the given overlay from your locally inst' + 'alled overlays. Specify "ALL" to remove all overlays') + + group.add_option('-s', + '--sync', + action = 'append', + help = 'Update the specified overlay. Use "ALL" as para' + 'meter to synchronize all overlays') + + group.add_option('-S', + '--sync-all', + action = 'store_true', + help = 'Update all overlays.') + + group.add_option('-L', + '--list', + action = 'store_true', + help = 'List the contents of the remote list.') + + group.add_option('-l', + '--list-local', + action = 'store_true', + help = 'List the locally installed overlays.') + + group.add_option('-f', + '--fetch', + action = 'store_true', + help = 'Fetch a remote list of overlays. This option is' + ' deprecated. The fetch operation will be performed by ' + 'default when you run sync, sync-all, or list.') + + group.add_option('-n', + '--nofetch', + action = 'store_true', + help = 'Do not fetch a remote list of overlays.') + + group.add_option('-p', + '--priority', + action = 'store', + help = 'Use this with the --add switch to set the prior' + 'ity of the added overlay. This will influence the sort' + 'ing order of the overlays in the PORTDIR_OVERLAY varia' + 'ble.') + + self.parser.add_option_group(group) + + #----------------------------------------------------------------- + # Additional Options + + group = OptionGroup(self.parser, + '') + + group.add_option('-c', + '--config', + action = 'store', + help = 'Path to the config file [default: ' \ + + self.defaults['config'] + '].') + + group.add_option('-o', + '--overlays', + action = 'append', + help = 'The list of overlays [default: ' \ + + self.defaults['overlays'] + '].') + + self.parser.add_option_group(group) + + #----------------------------------------------------------------- + # Output Options + + group = OptionGroup(self.parser, + '') + + group.add_option('-v', + '--verbose', + action = 'store_true', + help = 'Increase amount of output.') + + group.add_option('-q', + '--quiet', + action = 'store_true', + help = 'Yield no output. Please be careful with this op' + 'tion: If the processes spawned by layman when adding o' + 'r synchronizing overlays require any input layman will' + ' hang without telling you why. This might happen for e' + 'xample if your overlay resides in subversion and the S' + 'SL certificate of the server needs acceptance.') + + group.add_option('-Q', + '--quietness', + action = 'store', + type = 'int', + default = '4', + help = 'Set the level of output (0-4). Default: 4. Once' + ' you set this below 2 the same warning as given for --' + 'quiet applies! ') + + group.add_option('-k', + '--nocheck', + action = 'store_true', + help = 'Do not check overlay definitions and do not i' + 'ssue a warning if description or contact information' + ' are missing.') + + self.parser.add_option_group(group) + + #----------------------------------------------------------------- + # Debug Options + + OUT.cli_opts(self.parser) + + # Parse the command line first since we need to get the config + # file option. + (self.options, args) = self.parser.parse_args() + + # handle debugging + OUT.cli_handle(self.options) + + # Fetch only an alternate config setting from the options + if not self.options.__dict__['config'] is None: + self.defaults['config'] = self.options.__dict__['config'] + + OUT.debug('Got config file at ' + self.defaults['config'], 8) + + # Now parse the config file + self.config = ConfigParser.ConfigParser(self.defaults) + self.config.add_section('MAIN') + + # handle quietness + if self['quiet']: + OUT.set_info_level(1) + OUT.set_warn_level(1) + self.defaults['quietness'] = 0 + elif 'quietness' in self.keys(): + OUT.set_info_level(int(self['quietness'])) + OUT.set_warn_level(int(self['quietness'])) + + OUT.debug('Reading config file at ' + self.defaults['config'], 8) + + self.config.read(self.defaults['config']) + + def __getitem__(self, key): + + if key == 'overlays': + overlays = '' + if (key in self.options.__dict__.keys() + and not self.options.__dict__[key] is None): + overlays = '\n'.join(self.options.__dict__[key]) + if self.config.has_option('MAIN', 'overlays'): + overlays += '\n' + self.config.get('MAIN', 'overlays') + if overlays: + return overlays + + OUT.debug('Retrieving option', 8) + + if (key in self.options.__dict__.keys() + and not self.options.__dict__[key] is None): + return self.options.__dict__[key] + + OUT.debug('Retrieving option', 8) + + if self.config.has_option('MAIN', key): + if key == 'nocheck': + if self.config.get('MAIN', key).lower() == 'yes': + return True + else: + return False + return self.config.get('MAIN', key) + + OUT.debug('Retrieving option', 8) + + if key in self.defaults.keys(): + return self.defaults[key] + + OUT.debug('Retrieving option', 8) + + return None + + def keys(self): + '''Special handler for the configuration keys.''' + + OUT.debug('Retrieving keys', 8) + + keys = [i for i in self.options.__dict__.keys() + if not self.options.__dict__[i] is None] + + OUT.debug('Retrieving keys', 8) + + keys += [name for name, value in self.config.items('MAIN') + if not name in keys] + + OUT.debug('Retrieving keys', 8) + + keys += [i for i in self.defaults.keys() + if not i in keys] + + OUT.debug('Retrieving keys', 8) + + return keys + + +#=============================================================================== +# +# Testing +# +#------------------------------------------------------------------------------- + +if __name__ == '__main__': + import doctest + doctest.testmod(sys.modules[__name__]) diff --git a/db.py b/db.py new file mode 100644 index 0000000..ae32114 --- /dev/null +++ b/db.py @@ -0,0 +1,585 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN OVERLAY DB +################################################################################# +# File: db.py +# +# Access to the db of overlays +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# +'''Handles different storage files.''' + +__version__ = "$Id: db.py 309 2007-04-09 16:23:38Z wrobel $" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +import os, os.path, urllib2, re, md5 + +from layman.utils import path +from layman.overlay import Overlays + +from layman.debug import OUT + +#=============================================================================== +# +# Class DB +# +#------------------------------------------------------------------------------- + +class DB(Overlays): + ''' Handle the list of local overlays.''' + + def __init__(self, config): + + self.config = config + + self.path = config['local_list'] + + if config['nocheck']: + ignore = 2 + else: + ignore = 1 + + quiet = int(config['quietness']) < 3 + + Overlays.__init__(self, + [config['local_list'], ], + ignore, + quiet) + + OUT.debug('DB handler initiated', 6) + + def add(self, overlay): + ''' + Add an overlay to the local list of overlays. + + >>> write = os.tmpnam() + >>> write2 = os.tmpnam() + >>> write3 = os.tmpnam() + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> config = {'local_list' : + ... here + '/tests/testfiles/global-overlays.xml', + ... 'make_conf' : write2, + ... 'nocheck' : True, + ... 'storage' : write3, + ... 'quietness':3} + + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> a = DB(config) + >>> config['local_list'] = write + >>> b = DB(config) + >>> OUT.color_off() + + >>> m = MakeConf(config, b.overlays) + >>> m.path = write2 + >>> m.write() + + Commented out since it needs network access: + + # >>> b.add(a.select('wrobel-stable')) #doctest: +ELLIPSIS + # * Running command "/usr/bin/rsync -rlptDvz --progress --delete --delete-after --timeout=180 --exclude="distfiles/*" --exclude="local/*" --exclude="packages/*" "rsync://gunnarwrobel.de/wrobel-stable/*" "/tmp/file.../wrobel-stable""... + # >>> c = Overlays([write, ]) + # >>> c.overlays.keys() + # [u'wrobel-stable'] + + # >>> m = MakeConf(config, b.overlays) + # >>> [i.name for i in m.overlays] #doctest: +ELLIPSIS + # [u'wrobel-stable'] + + + # >>> os.unlink(write) + >>> os.unlink(write2) + >>> import shutil + + # >>> shutil.rmtree(write3) + ''' + + if overlay.name not in self.overlays.keys(): + result = overlay.add(self.config['storage']) + if result == 0: + if 'priority' in self.config.keys(): + overlay.priority = int(self.config['priority']) + self.overlays[overlay.name] = overlay + self.write(self.path) + make_conf = MakeConf(self.config, self.overlays) + make_conf.add(overlay) + else: + overlay.delete(self.config['storage']) + raise Exception('Adding the overlay failed!') + else: + raise Exception('Overlay "' + overlay.name + '" already in the loca' + 'l list!') + + def delete(self, overlay): + ''' + Add an overlay to the local list of overlays. + + >>> write = os.tmpnam() + >>> write2 = os.tmpnam() + >>> write3 = os.tmpnam() + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> config = {'local_list' : + ... here + '/tests/testfiles/global-overlays.xml', + ... 'make_conf' : write2, + ... 'nocheck' : True, + ... 'storage' : write3, + ... 'quietness':3} + + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> a = DB(config) + >>> config['local_list'] = write + >>> b = DB(config) + >>> OUT.color_off() + + >>> m = MakeConf(config, b.overlays) + >>> m.path = here + '/tests/testfiles/make.conf' + >>> m.read() + + >>> m.path = write2 + >>> m.write() + + # >>> b.add(a.select('wrobel-stable')) #doctest: +ELLIPSIS + # * Running command "/usr/bin/rsync -rlptDvz --progress --delete --delete-after --timeout=180 --exclude="distfiles/*" --exclude="local/*" --exclude="packages/*" "rsync://gunnarwrobel.de/wrobel-stable/*" "/tmp/file.../wrobel-stable""... + # >>> b.add(a.select('wrobel')) #doctest: +ELLIPSIS + # * Running command "/usr/bin/svn co "https://overlays.gentoo.org/svn/dev/wrobel/" "/tmp/file.../wrobel""... + # >>> c = Overlays([write, ]) + # >>> c.overlays.keys() + # [u'wrobel', u'wrobel-stable'] + + # >>> b.delete(b.select('wrobel')) + # >>> c = Overlays([write, ]) + # >>> c.overlays.keys() + # [u'wrobel-stable'] + + # >>> m = MakeConf(config, b.overlays) + # >>> [i.name for i in m.overlays] #doctest: +ELLIPSIS + # [u'wrobel-stable'] + + # >>> os.unlink(write) + >>> os.unlink(write2) + >>> import shutil + + # >>> shutil.rmtree(write3) + ''' + + if overlay.name in self.overlays.keys(): + make_conf = MakeConf(self.config, self.overlays) + overlay.delete(self.config['storage']) + del self.overlays[overlay.name] + self.write(self.path) + make_conf.delete(overlay) + else: + raise Exception('No local overlay named "' + overlay.name + '"!') + + def sync(self, overlay_name): + '''Synchronize the given overlay.''' + + overlay = self.select(overlay_name) + + if overlay: + result = overlay.sync(self.config['storage']) + if result: + raise Exception('Syncing overlay "' + overlay_name + + '" returned status ' + str(result) + '!') + else: + raise Exception('No such overlay ("' + overlay_name + '")!') + +#=============================================================================== +# +# Class RemoteDB +# +#------------------------------------------------------------------------------- + +class RemoteDB(Overlays): + '''Handles fetching the remote overlay list.''' + + def __init__(self, config): + + self.config = config + + self.proxies = {} + + if config['proxy']: + self.proxies['http'] = config['proxy'] + elif os.getenv('http_proxy'): + self.proxies['http'] = os.getenv('http_proxy') + + if self.proxies: + proxy_handler = urllib2.ProxyHandler(self.proxies) + opener = urllib2.build_opener(proxy_handler) + urllib2.install_opener(opener) + + self.urls = [i.strip() for i in config['overlays'].split('\n') if i] + + paths = [self.path(i) for i in self.urls] + + if config['nocheck']: + ignore = 2 + else: + ignore = 0 + + quiet = int(config['quietness']) < 3 + + Overlays.__init__(self, paths, ignore, quiet) + + def cache(self): + ''' + Copy the remote overlay list to the local cache. + + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> cache = os.tmpnam() + >>> config = {'overlays' : + ... 'file://' + here + '/tests/testfiles/global-overlays.xml', + ... 'cache' : cache, + ... 'nocheck' : True, + ... 'proxy' : None, + ... 'quietness':3} + >>> a = RemoteDB(config) + >>> a.cache() + >>> b = open(a.path(config['overlays'])) + >>> b.readlines()[24] + ' A collection of ebuilds from Gunnar Wrobel [wrobel@gentoo.org].\\n' + + >>> b.close() + >>> os.unlink(a.path(config['overlays'])) + + >>> a.overlays.keys() + [u'wrobel', u'wrobel-stable'] + ''' + for url in self.urls: + + mpath = self.path(url) + + try: + + # Fetch the remote list + olist = urllib2.urlopen(url).read() + + # Create our storage directory if it is missing + if not os.path.exists(os.path.dirname(mpath)): + try: + os.makedirs(os.path.dirname(mpath)) + except OSError, error: + raise OSError('Failed to create layman storage direct' + + 'ory ' + os.path.dirname(mpath) + '\n' + + 'Error was:' + str(error)) + + # Before we overwrite the old cache, check that the downloaded + # file is intact and can be parsed + try: + self.read(olist) + except Exception, error: + raise IOError('Failed to parse the overlays list fetched fr' + 'om ' + url + '\nThis means that the download' + 'ed file is somehow corrupt or there was a pr' + 'oblem with the webserver. Check the content ' + 'of the file. Error was:\n' + str(error)) + + # Ok, now we can overwrite the old cache + try: + out_file = open(mpath, 'w') + out_file.write(olist) + out_file.close() + + except Exception, error: + raise IOError('Failed to temporarily cache overlays list in' + ' ' + mpath + '\nError was:\n' + str(error)) + + + except IOError, error: + OUT.warn('Failed to update the overlay list from: ' + + url + '\nError was:\n' + str(error)) + + try: + # Finally parse the contents of the cache + self.read_file(mpath) + except IOError, error: + OUT.warn('Failed to read a cached version of the overlay list f' + 'rom ' + url + '. You probably did not download the fi' + 'le before. The corresponding entry in your layman.cfg' + ' file will be disregarded.\nError was:\n' + str(error) + ) + + def path(self, url): + '''Return a unique file name for the url.''' + + base = self.config['cache'] + + OUT.debug('Generating cache path.', 6) + + return base + '_' + md5.md5(url).hexdigest() + '.xml' + +#=============================================================================== +# +# Helper class MakeConf +# +#------------------------------------------------------------------------------- + +class MakeConf: + ''' + Handles modifications to /etc/make.conf + + Check that an add/remove cycle does not modify the make.conf: + + >>> import md5 + >>> write = os.tmpnam() + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> config = {'local_list' : + ... here + '/tests/testfiles/global-overlays.xml', + ... 'make_conf' : here + '/tests/testfiles/make.conf', + ... 'nocheck' : True, + ... 'storage' : '/usr/portage/local/layman', + ... 'quietness':3} + >>> b = DB(config) + >>> a = MakeConf(config, b.overlays) + >>> o_md5 = str(md5.md5(open(here + '/tests/testfiles/make.conf').read()).hexdigest()) + >>> a.path = write + >>> a.add(b.overlays['wrobel-stable']) + >>> [i.name for i in a.overlays] + [u'wrobel-stable', u'wrobel-stable'] + >>> a.add(b.overlays['wrobel']) + >>> [i.name for i in a.overlays] + [u'wrobel', u'wrobel-stable', u'wrobel-stable'] + >>> a.delete(b.overlays['wrobel-stable']) + >>> [i.name for i in a.overlays] + [u'wrobel'] + >>> a.add(b.overlays['wrobel-stable']) + >>> [i.name for i in a.overlays] + [u'wrobel', u'wrobel-stable'] + >>> a.delete(b.overlays['wrobel']) + >>> n_md5 = str(md5.md5(open(write).read()).hexdigest()) + >>> o_md5 == n_md5 + True + >>> os.unlink(write) + ''' + + my_re = re.compile('PORTDIR_OVERLAY\s*=\s*"([^"]*)"') + + def __init__(self, config, overlays): + + self.path = config['make_conf'] + self.storage = config['storage'] + self.data = '' + self.db = overlays + self.overlays = [] + self.extra = [] + + self.read() + + def add(self, overlay): + ''' + Add an overlay to make.conf. + + >>> write = os.tmpnam() + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> config = {'local_list' : + ... here + '/tests/testfiles/global-overlays.xml', + ... 'make_conf' : here + '/tests/testfiles/make.conf', + ... 'nocheck' : True, + ... 'storage' : '/usr/portage/local/layman', + ... 'quietness':3} + >>> c = DB(config) + >>> a = MakeConf(config, c.overlays) + >>> a.path = write + >>> a.add(c.select('wrobel')) + >>> config['make_conf'] = write + >>> b = MakeConf(config, c.overlays) + >>> [i.name for i in b.overlays] + [u'wrobel', u'wrobel-stable'] + >>> b.extra + ['/usr/portage/local/ebuilds/testing', '/usr/portage/local/ebuilds/stable', '/usr/portage/local/kolab2', '/usr/portage/local/gentoo-webapps-overlay/experimental', '/usr/portage/local/gentoo-webapps-overlay/production-ready'] + + >>> os.unlink(write) + ''' + self.overlays.append(overlay) + self.write() + + def delete(self, overlay): + ''' + Delete an overlay from make.conf. + + >>> write = os.tmpnam() + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> config = {'local_list' : + ... here + '/tests/testfiles/global-overlays.xml', + ... 'make_conf' : here + '/tests/testfiles/make.conf', + ... 'nocheck' : True, + ... 'storage' : '/usr/portage/local/layman', + ... 'quietness':3} + >>> c = DB(config) + >>> a = MakeConf(config, c.overlays) + >>> a.path = write + >>> a.delete(c.select('wrobel-stable')) + >>> config['make_conf'] = write + >>> b = MakeConf(config, c.overlays) + >>> [i.name for i in b.overlays] + [] + >>> b.extra + ['/usr/portage/local/ebuilds/testing', '/usr/portage/local/ebuilds/stable', '/usr/portage/local/kolab2', '/usr/portage/local/gentoo-webapps-overlay/experimental', '/usr/portage/local/gentoo-webapps-overlay/production-ready'] + + >>> os.unlink(write) + ''' + self.overlays = [i + for i in self.overlays + if i.name != overlay.name] + self.write() + + def read(self): + ''' + Read the list of registered overlays from /etc/make.conf. + + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> config = {'local_list' : + ... here + '/tests/testfiles/global-overlays.xml', + ... 'make_conf' : here + '/tests/testfiles/make.conf', + ... 'nocheck' : True, + ... 'storage' : '/usr/portage/local/layman', + ... 'quietness':3} + >>> c = DB(config) + >>> a = MakeConf(config, c.overlays) + >>> [i.name for i in a.overlays] + [u'wrobel-stable'] + >>> a.extra + ['/usr/portage/local/ebuilds/testing', '/usr/portage/local/ebuilds/stable', '/usr/portage/local/kolab2', '/usr/portage/local/gentoo-webapps-overlay/experimental', '/usr/portage/local/gentoo-webapps-overlay/production-ready'] + ''' + if os.path.isfile(self.path): + self.content() + + overlays = self.my_re.search(self.data) + + if not overlays: + raise Exception('Did not find a PORTDIR_OVERLAY entry in file ' + + self.path +'! Did you specify the correct file?') + + overlays = [i.strip() + for i in overlays.group(1).split('\n') + if i.strip()] + + for i in overlays: + if i[:len(self.storage)] == self.storage: + oname = os.path.basename(i) + if oname in self.db.keys(): + self.overlays.append(self.db[oname]) + else: + # These are additional overlays that we dont know + # anything about. The user probably added them manually + self.extra.append(i) + else: + # These are additional overlays that we dont know anything + # about. The user probably added them manually + self.extra.append(i) + + + else: + self.overlays = [] + self.data = 'PORTDIR_OVERLAY="\n"\n' + + self.extra = [i for i in self.extra + if (i != '$PORTDIR_OVERLAY' + and i != '${PORTDIR_OVERLAY}')] + + def write(self): + ''' + Write the list of registered overlays to /etc/make.conf. + + >>> write = os.tmpnam() + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> config = {'local_list' : + ... here + '/tests/testfiles/global-overlays.xml', + ... 'make_conf' : here + '/tests/testfiles/make.conf', + ... 'nocheck' : True, + ... 'storage' : '/usr/portage/local/layman', + ... 'quietness':3} + >>> c = DB(config) + >>> a = MakeConf(config, c.overlays) + >>> a.path = write + >>> a.write() + >>> config['make_conf'] = write + >>> b = MakeConf(config, c.overlays) + >>> [i.name for i in b.overlays] + [u'wrobel-stable'] + >>> b.extra + ['/usr/portage/local/ebuilds/testing', '/usr/portage/local/ebuilds/stable', '/usr/portage/local/kolab2', '/usr/portage/local/gentoo-webapps-overlay/experimental', '/usr/portage/local/gentoo-webapps-overlay/production-ready'] + + >>> os.unlink(write) + ''' + def prio_sort(a, b): + '''Sort by priority.''' + if a.priority < b.priority: + return -1 + elif a.priority > b.priority: + return 1 + return 0 + + self.overlays.sort(prio_sort) + + paths = [] + for i in self.overlays: + paths.append(path((self.storage, i.name, ))) + + overlays = 'PORTDIR_OVERLAY="\n' + overlays += '\n'.join(paths) + '\n' + overlays += '$PORTDIR_OVERLAY\n' + overlays += '\n'.join(self.extra) + overlays += '"' + + content = self.my_re.sub(overlays, self.data) + + if not self.my_re.search(content): + raise Exception('Ups, failed to set a proper PORTDIR_OVERLAY entry ' + 'in file ' + self.path +'! Did not overwrite the fi' + 'le.') + + try: + make_conf = open(self.path, 'w') + + make_conf.write(content) + + make_conf.close() + + except Exception, error: + raise Exception('Failed to read "' + self.path + '".\nError was:\n' + + str(error)) + + def content(self): + ''' + Returns the content of the /etc/make.conf file. + ''' + try: + make_conf = open(self.path) + + self.data = make_conf.read() + + make_conf.close() + + except Exception, error: + raise Exception('Failed to read "' + self.path + '".\nError was:\n' + + str(error)) + +#=============================================================================== +# +# Testing +# +#------------------------------------------------------------------------------- + +if __name__ == '__main__': + import doctest, sys + + # Ignore warnings here. We are just testing + from warnings import filterwarnings, resetwarnings + filterwarnings('ignore') + + doctest.testmod(sys.modules[__name__]) + + resetwarnings() diff --git a/debug.py b/debug.py new file mode 100644 index 0000000..925f49c --- /dev/null +++ b/debug.py @@ -0,0 +1,501 @@ +################################################################################# +# LAYMAN - DEBUGGING FUNCTIONS +################################################################################# +# debug.py -- Utility function for debugging +# Copyright 2005 - 2006 Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 + +__version__ = "$Id: debug.py 153 2006-06-05 06:03:16Z wrobel $" + +################################################################################# +## +## Dependancies +## +################################################################################# + +import sys, inspect + +from optparse import OptionGroup + +################################################################################# +## +## Color codes (taken from portage) +## +################################################################################# + +esc_seq = '\x1b[' + +codes = {} +codes['reset'] = esc_seq + '39;49;00m' +codes['red'] = esc_seq + '31;01m' +codes['green'] = esc_seq + '32;01m' +codes['yellow'] = esc_seq + '33;01m' +codes['turquoise'] = esc_seq + '36;01m' + +################################################################################# +## +## Message Class +## +################################################################################# + +class Message: + #FIXME: Think about some simple doctests before you modify this class the + # next time. + + def __init__(self, module = '', + err = sys.stderr, + dbg = sys.stderr, + debugging_level = 4, + debugging_verbosity = 2, + info_level = 4, + warn_level = 4, + col = True, + mth = ['*'], + obj = ['*'], + var = ['*']): + + # A description of the module that is being debugged + self.debug_env = module + + # Where should the debugging output go? This can also be a file + self.debug_out = dbg + + # Where should the error output go? This can also be a file + self.error_out = err + + # The higher the level the more information you will get + self.warn_lev = warn_level + + # The higher the level the more information you will get + self.info_lev = info_level + + # The highest level of debugging messages acceptable for output + # The higher the level the more output you will get + self.debug_lev = debugging_level + + # The debugging output can range from very verbose (3) to + # very compressed (1) + self.debug_vrb = debugging_verbosity + + # Which methods should actually be debugged? + # Use '*' to indicate 'All methods' + self.debug_mth = mth + + # Which objects should actually be debugged? + # Use '*' to indicate 'All objects' + self.debug_obj = obj + + # Which variables should actually be debugged? + # Use '*' to indicate 'All variables' + self.debug_var = var + + # Exclude class variables by default + self.show_class_variables = False + + # Should the output be colored? + self.use_color = col + + self.has_error = False + + + ############################################################################ + # Add command line options + + def cli_opts(self, parser): + + group = OptionGroup(parser, + '', + 'Control the debugging features of ' + + self.debug_env) + + group.add_option('--debug', + action = 'store_true', + help = 'Activates debugging features.') + + group.add_option('--debug-level', + action = 'store', + type = 'int', + help = 'A value between 0 and 10. 0 means no debugging ' + 'messages will be selected, 10 selects all debugging me' + 'ssages. Default is "4".') + + group.add_option('--debug-verbose', + action = 'store', + type = 'int', + help = 'A value between 1 and 3. Lower values yield les' + 's verbose debugging output. Default is "2".') + + group.add_option('--debug-methods', + action = 'store', + help = 'Limits the methods that will return debugging o' + 'utput. The function name is sufficient and there is no' + 'difference between class methods or general functions.' + ' Several methods can be specified by seperating them w' + ' with a comma. Default is "*" which specifies all meth' + 'ods.') + + group.add_option('--debug-classes', + action = 'store', + help = 'Limits the classes that will return debugging o' + 'utput. Specify only the class name not including the m' + 'odules in which the class is defined (e.g. MyModule.ma' + 'in.Main should only be represented by "Main"). Several' + 'classes can be specified by seperating them with a com' + 'ma. Default is "*" which specifies all classes.') + + group.add_option('--debug-variables', + action = 'store', + help = 'Limits the variables that will return debugging' + ' output. Several variables can be specified by seperat' + 'ing them with a comma. Default is "*" which specifies ' + 'all variables.') + + group.add_option('--debug-class-vars', + action = 'store_true', + help = 'In default mode the debugging code will only re' + 'turn information on the local variable which does not ' + 'include the class variables. Use this switch to add al' + 'l values that are provided by "self".') + + group.add_option('--debug-nocolor', + action = 'store_true', + help = 'Deactivates colors in the debugging output.') + + parser.add_option_group(group) + + + ############################################################################# + # Handle command line options + + def cli_handle(self, options): + + if (options.__dict__.has_key('debug') + and options.__dict__['debug']): + self.debug_on() + else: + self.debug_off() + return + + if (options.__dict__.has_key('debug_class_vars') + and options.__dict__['debug_class_vars']): + self.class_variables_on() + else: + self.class_variables_off() + + if (options.__dict__.has_key('debug_nocolor') + and options.__dict__['debug_nocolor']): + self.color_off() + else: + self.color_on() + + if (options.__dict__.has_key('debug_level') and + options.__dict__['debug_level']): + dbglvl = int(options.__dict__['debug_level']) + if dbglvl < 0: + dbglvl = 0 + if dbglvl > 10: + dbglvl = 10 + self.set_debug_level(dbglvl) + + if (options.__dict__.has_key('debug_verbose') and + options.__dict__['debug_verbose']): + dbgvrb = int(options.__dict__['debug_verbose']) + if dbgvrb < 1: + dbgvrb = 1 + if dbgvrb > 3: + dbgvrb = 3 + self.set_debug_verbosity(dbgvrb) + + for i in [('debug_methods', self.set_debug_methods), + ('debug_classes', self.set_debug_classes), + ('debug_variables', self.set_debug_variables),]: + + if (options.__dict__.has_key(i[0]) and + options.__dict__[i[0]]): + i[1](options.__dict__[i[0]]) + + + ############################################################################# + ## Helper Functions + + def set_module(self, module): + + self.debug_env = module + + def set_debug_methods(self, methods): + + methods = methods.split(',') + + if methods: + self.debug_mth = methods + + def set_debug_classes(self, classes): + + classes = classes.split(',') + + if classes: + self.debug_obj = classes + + def set_debug_variables(self, variables): + + variables = variables.split(',') + + if variables: + self.debug_var = variables + + def maybe_color (self, col, text): + if self.use_color: + return codes[col] + text + codes['reset'] + return text + + def set_info_level(self, info_level = 4): + self.info_lev = info_level + + def info_off(self): + self.set_info_level(0) + + def info_on(self, info_level = 4): + self.set_info_level(info_level) + + def set_warn_level(self, warn_level = 4): + self.warn_lev = warn_level + + def warn_off(self): + self.set_warn_level(0) + + def warn_on(self, warn_level = 4): + self.set_warn_level(warn_level) + + def set_debug_level(self, debugging_level = 4): + self.debug_lev = debugging_level + + def set_debug_verbosity(self, debugging_verbosity = 2): + self.debug_vrb = debugging_verbosity + + def debug_off(self): + self.set_debug_level(0) + + def debug_on(self): + self.set_debug_level() + + def color_off(self): + self.use_color = False + + def color_on(self): + self.use_color = True + + def class_variables_off(self): + self.show_class_variables = False + + def class_variables_on(self): + self.show_class_variables = True + + ############################################################################# + ## Output Functions + + def notice (self, note): + print note + + def info (self, info, level = 4): + + info = str(info) + + if level > self.info_lev: + return + + for i in info.split('\n'): + print self.maybe_color('green', '* ') + i + + def status (self, message, status, info = 'ignored'): + + message = str(message) + + lines = message.split('\n') + + if not lines: + return + + for i in lines[0:-1]: + print self.maybe_color('green', '* ') + i + + i = lines[-1] + + if len(i) > 58: + i = i[0:57] + + if status == 1: + result = '[' + self.maybe_color('green', 'ok') + ']' + elif status == 0: + result = '[' + self.maybe_color('red', 'failed') + ']' + else: + result = '[' + self.maybe_color('yellow', info) + ']' + + print self.maybe_color('green', '* ') + i + ' ' + '.' * (58 - len(i)) \ + + ' ' + result + + def warn (self, warn, level = 4): + + warn = str(warn) + + if level > self.warn_lev: + return + + for i in warn.split('\n'): + print self.maybe_color('yellow', '* ') + i + + def error (self, error): + + error = str(error) + + for i in error.split('\n'): + print >> self.error_out, self.maybe_color('red', '* ') + i + self.has_error = True + + def die (self, error): + + error = str(error) + + for i in error.split('\n'): + self.error(self.maybe_color('red', 'Fatal error: ') + i) + self.error(self.maybe_color('red', 'Fatal error(s) - aborting')) + sys.exit(1) + + def debug (self, message, level = 4): + ''' + This is a generic debugging method. + ''' + ## Check the debug level first. This is the most inexpensive check. + if level > self.debug_lev: + return + + ## Maybe this should be debugged. So get the stack first. + stack = inspect.stack() + + ## This can probably never happen but does not harm to check + ## that there is actually something calling this function + if len(stack) < 2: + return + + ## Get the stack length to determine indentation of the debugging output + stacklength = len(stack) + ls = ' ' * stacklength + + ## Get the information about the caller + caller = stack[1] + + ## The function name of the calling frame is the fourth item in the list + callermethod = caller[3] + + ## Is this actually one of the methods that should be debugged? + if not '*' in self.debug_mth and not callermethod in self.debug_mth: + return + + ## Still looks like this should be debugged. So retrieve the dictionary + ## of local variables from the caller + callerlocals = inspect.getargvalues(caller[0])[3] + + ## Is the caller an obejct? If so he provides 'self' + if 'self' in callerlocals.keys(): + callerobject = callerlocals['self'] + del callerlocals['self'] + if self.show_class_variables: + cv = inspect.getmembers(callerobject, + lambda x: not inspect.ismethod(x)) + callerlocals.sync(cv) + else: + callerobject = None + + # Remove variables not requested + if not '*' in self.debug_var: + callerlocals = dict([i for i in callerlocals.items() + if i[0] in self.debug_var]) + + ## Is the object among the list of objects to debug? + if (not '*' in self.debug_obj and + not str(callerobject.__class__.__name__) in self.debug_obj): + return + + message = str(message) + + def breaklines(x): + ''' + Helper function to keep width of the debugging output. + + This may look ugly for arrays but it is acceptable and not + breaking the line would break the output format + ''' + ## Get the number of lines we need (rounded down) + lines = len(x) // 60 + if lines > 0: + for j in range(lines): + ## Print line with continuation marker + print >> self.debug_out, ls + '// ' + x[0:60] + ' \\' + ## Remove printed characters from output + x = x[60:] + ## Print final line + print >> self.debug_out, ls + '// ' + x + + if self.debug_vrb == 1: + # Top line indicates class and method + c = '' + if callerobject: + c += 'Class: ' + str(callerobject.__class__.__name__) + ' | ' + if callermethod: + c += 'Method: ' + str(callermethod) + print >> self.debug_out, '// ' + c + # Selected variables follow + if callerlocals: + for i,j in callerlocals.items(): + print >> self.debug_out, '// ' \ + + self.maybe_color('turquoise', str(i)) + ':' + str(j) + # Finally the message + print >> self.debug_out, self.maybe_color('yellow', message) + return + + if self.debug_vrb == 3: + print >> self.debug_out, ls + '/////////////////////////////////' + \ + '////////////////////////////////' + + # General information about what is being debugged + #(module name or similar) + print >> self.debug_out, ls + '// ' + self.debug_env + print >> self.debug_out, ls + '//-----------------------------------' + \ + '----------------------------' + + ## If the caller is a class print the name here + if callerobject: + print >> self.debug_out, ls + \ + '// Object Class: ' + str(callerobject.__class__.__name__) + + ## If the method has been extracted print it here + if callermethod: + print >> self.debug_out, ls + '// ' \ + + self.maybe_color('green', 'Method: ') + str(callermethod) + if self.debug_vrb == 3: + print >> self.debug_out, ls + '//---------------------------' + \ + '------------------------------------' + + ## Print the information on all available local variables + if callerlocals: + if self.debug_vrb == 3: + print >> self.debug_out, ls + '//' + print >> self.debug_out, ls + '// VALUES ' + for i,j in callerlocals.items(): + print >> self.debug_out, ls + '// ------------------> ' \ + + self.maybe_color('turquoise', str(i)) + ':' + breaklines(str(j)) + if self.debug_vrb == 3: + print >> self.debug_out, ls + '//------------------------------'\ + '---------------------------------' + + # Finally print the message + breaklines(self.maybe_color('yellow', message)) + + if self.debug_vrb == 3: + print >> self.debug_out, ls + '//-------------------------------' + \ + '--------------------------------' + print >> self.debug_out, ls + '/////////////////////////////////' + \ + '////////////////////////////////' + +## gloabal message handler +OUT = Message('layman') diff --git a/overlay.py b/overlay.py new file mode 100644 index 0000000..38e9364 --- /dev/null +++ b/overlay.py @@ -0,0 +1,247 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN OVERLAY HANDLER +################################################################################# +# File: overlay.py +# +# Access to an xml list of overlays +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# +'''Main handler for overlays.''' + +__version__ = "$Id: overlay.py 273 2006-12-30 15:54:50Z wrobel $" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +import os, os.path, xml.dom.minidom + +from layman.overlays.bzr import BzrOverlay +from layman.overlays.darcs import DarcsOverlay +from layman.overlays.git import GitOverlay +from layman.overlays.mercurial import MercurialOverlay +from layman.overlays.cvs import CvsOverlay +from layman.overlays.svn import SvnOverlay +from layman.overlays.rsync import RsyncOverlay +from layman.overlays.tar import TarOverlay + +from layman.debug import OUT + +#=============================================================================== +# +# Constants +# +#------------------------------------------------------------------------------- + +OVERLAY_TYPES = {'git' : GitOverlay, + 'cvs' : CvsOverlay, + 'svn' : SvnOverlay, + 'rsync' : RsyncOverlay, + 'tar' : TarOverlay, + 'bzr' : BzrOverlay, + 'mercurial' : MercurialOverlay, + 'darcs' : DarcsOverlay} + +#=============================================================================== +# +# Class Overlays +# +#------------------------------------------------------------------------------- + +class Overlays: + ''' Handle a list of overlays.''' + + def __init__(self, paths, ignore = 0, quiet = False): + + self.quiet = quiet + self.paths = paths + self.ignore = ignore + + self.overlays = {} + + OUT.debug('Initializing overlay list handler', 8) + + for path in self.paths: + if os.path.exists(path): + self.read_file(path) + + def read_file(self, path): + '''Read the overlay definition file.''' + + try: + + document = open(path).read() + + except Exception, error: + raise IOError('Failed to read the overlay list at ("' + + path + '")!\nError was:\n' + str(error)) + + + self.read(document) + + def read(self, document): + ''' + Read an xml list of overlays. + + >>> here = os.path.dirname(os.path.realpath(__file__)) + + >>> a = Overlays([here + '/tests/testfiles/global-overlays.xml', ]) + >>> a.overlays.keys() + [u'wrobel', u'wrobel-stable'] + + >>> a.overlays['wrobel-stable'].data['&src'] + u'rsync://gunnarwrobel.de/wrobel-stable' + ''' + try: + + document = xml.dom.minidom.parseString(document) + + except Exception, error: + raise Exception('Failed to parse the overlay list!\nError was:\n' + + str(error)) + + overlays = document.getElementsByTagName('overlay') + + for overlay in overlays: + + OUT.debug('Parsing overlay entry', 8) + + for index in range(0, overlay.attributes.length): + attr = overlay.attributes.item(index) + if attr.name == 'type': + if attr.nodeValue in OVERLAY_TYPES.keys(): + try: + ovl = OVERLAY_TYPES[attr.nodeValue](overlay, + self.ignore, + self.quiet) + self.overlays[ovl.name] = ovl + except Exception, error: + OUT.warn(str(error), 3) + else: + raise Exception('Unknown overlay type "' + + attr.nodeValue + '"!') + + def write(self, path): + ''' + Write the list of overlays to a file. + + >>> write = os.tmpnam() + >>> here = os.path.dirname(os.path.realpath(__file__)) + + >>> a = Overlays([here + '/tests/testfiles/global-overlays.xml', ]) + >>> b = Overlays([write,]) + >>> b.overlays['wrobel-stable'] = a.overlays['wrobel-stable'] + >>> b.write(write) + >>> c = Overlays([write,]) + >>> c.overlays.keys() + [u'wrobel-stable'] + + >>> os.unlink(write) + ''' + + imp = xml.dom.minidom.getDOMImplementation() + + doc = imp.createDocument('layman', 'overlays', None) + + root = doc.childNodes[0] + + for name, overlay in self.overlays.items(): + + root.appendChild(overlay.to_minidom(doc)) + + try: + + out_file = open(path, 'w') + + doc.writexml(out_file, '', ' ', '\n') + + except Exception, error: + raise Exception('Failed to write to local overlays file: ' + + path + '\nError was:\n' + str(error)) + + def select(self, overlay): + ''' + Select an overlay from the list. + + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> a = Overlays([here + '/tests/testfiles/global-overlays.xml', ]) + >>> a.select('wrobel-stable').data['&src'] + u'rsync://gunnarwrobel.de/wrobel-stable' + ''' + + if overlay in self.overlays.keys(): + return self.overlays[overlay] + + def list(self, verbose = False): + ''' + List all overlays. + + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> a = Overlays([here + '/tests/testfiles/global-overlays.xml', ]) + >>> for i in a.list(True): + ... print i[0] + wrobel + ~~~~~~ + Source : https://overlays.gentoo.org/svn/dev/wrobel + Contact : nobody@gentoo.org + Type : Subversion; Priority: 10 + + Description: + Test + + wrobel-stable + ~~~~~~~~~~~~~ + Source : rsync://gunnarwrobel.de/wrobel-stable + Contact : nobody@gentoo.org + Type : Rsync; Priority: 50 + + Description: + A collection of ebuilds from Gunnar Wrobel [wrobel@gentoo.org]. + + + >>> for i in a.list(False): + ... print i[0] + wrobel [Subversion] (source: https://overlays.gentoo.or...) + wrobel-stable [Rsync ] (source: rsync://gunnarwrobel.de/wr...) + ''' + result = [] + + for name, overlay in self.overlays.items(): + + if verbose: + result.append((str(overlay), overlay.is_supported(), + overlay.is_official())) + else: + result.append((overlay.short_list(), overlay.is_supported(), + overlay.is_official())) + + result = sorted(result) + + return result + +#=============================================================================== +# +# Testing +# +#------------------------------------------------------------------------------- + +if __name__ == '__main__': + import doctest, sys + + # Ignore warnings here. We are just testing + from warnings import filterwarnings, resetwarnings + filterwarnings('ignore') + + doctest.testmod(sys.modules[__name__]) + + resetwarnings() diff --git a/overlays/.svn.ignore b/overlays/.svn.ignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/overlays/.svn.ignore @@ -0,0 +1 @@ +*.pyc diff --git a/overlays/__init__.py b/overlays/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/overlays/__init__.py @@ -0,0 +1 @@ +# diff --git a/overlays/bzr.py b/overlays/bzr.py new file mode 100644 index 0000000..8e8bb47 --- /dev/null +++ b/overlays/bzr.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN BZR OVERLAY HANDLER +################################################################################# +# File: bzr.py +# +# Handles bzr overlays +# +# Copyright: +# (c) 2005 - 2006 Adrian Perez, Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Adrian Perez +# Gunnar Wrobel +# +'''Should work with any version of Bzr equal to or better than 0.7 -- + caution: tested only with 0.8 and 0.8.2...''' + +__version__ = "$Id: bzr.py 236 2006-09-05 20:39:37Z wrobel $" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +from layman.utils import path +from layman.overlays.overlay import Overlay + +#=============================================================================== +# +# Class BzrOverlay +# +#------------------------------------------------------------------------------- + +class BzrOverlay(Overlay): + ''' Handles bzr overlays.''' + + type = 'Bzr' + + binary_command = '/usr/bin/bzr' + + def add(self, base): + '''Add overlay.''' + + self.supported() + + return self.cmd(self.binary_command + ' get "' + self.src + '/" "' +\ + path([base, self.name]) + '"') + + def sync(self, base): + '''Sync overlay.''' + + self.supported() + + return self.cmd('cd "' + path([base, self.name]) + '" && ' + \ + self.binary_command + ' pull --overwrite "' + self.src \ + + '"') + + def supported(self): + '''Overlay type supported?''' + + return Overlay.supported(self, [(self.binary_command, 'bzr', + 'dev-util/bzr'),]) diff --git a/overlays/cvs.py b/overlays/cvs.py new file mode 100644 index 0000000..95f20ea --- /dev/null +++ b/overlays/cvs.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN CVS OVERLAY HANDLER +################################################################################# +# File: cvs.py +# +# Handles cvs overlays +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# +''' Cvs overlay support.''' + +__version__ = "$Id$" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +from layman.utils import path +from layman.overlays.overlay import Overlay + +#=============================================================================== +# +# Class CvsOverlay +# +#------------------------------------------------------------------------------- + +class CvsOverlay(Overlay): + ''' Handles cvs overlays.''' + + type = 'cvs' + + binary = '/usr/bin/cvs' + + def __init__(self, xml, ignore = 0, quiet = False): + + Overlay.__init__(self, xml, ignore, quiet) + + if '&subpath' in self.data.keys(): + self.subpath = self.data['&subpath'] + else: + self.subpath = '' + + def add(self, base): + '''Add overlay.''' + + self.supported() + + return self.cmd('cd "' + base + '" && CVSROOT="' + self.src + '" ' + + self.binary + ' co -d "' + self.name + + '" "' + self.subpath + '"' ) + + def sync(self, base): + '''Sync overlay.''' + + self.supported() + + return self.cmd('cd "' + path([base, self.name]) + '" && ' + + self.binary + ' update') + + def supported(self): + '''Overlay type supported?''' + + return Overlay.supported(self, [(self.binary, 'cvs', + 'dev-util/cvs'),]) diff --git a/overlays/darcs.py b/overlays/darcs.py new file mode 100644 index 0000000..56e6d91 --- /dev/null +++ b/overlays/darcs.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN DARCS OVERLAY HANDLER +################################################################################# +# File: darcs.py +# +# Handles darcs overlays +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel, Andres Loeh +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# Andres Loeh +# +''' Darcs overlay support.''' + +__version__ = "$Id: darcs.py 236 2006-09-05 20:39:37Z wrobel $" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +from layman.utils import path +from layman.overlays.overlay import Overlay + +#=============================================================================== +# +# Class BzrOverlay +# +#------------------------------------------------------------------------------- + +class DarcsOverlay(Overlay): + ''' Handles darcs overlays.''' + + type = 'Darcs' + + binary_command = '/usr/bin/darcs' + + def add(self, base): + '''Add overlay.''' + + self.supported() + + return self.cmd(self.binary_command + ' get --partial "' + self.src + + '/" "' + path([base, self.name]) + '"') + + def sync(self, base): + '''Sync overlay.''' + + self.supported() + + return self.cmd('cd "' + path([base, self.name]) + '" && ' + + self.binary_command + ' pull --all "' + self.src + '"') + + def supported(self): + '''Overlay type supported?''' + + return Overlay.supported(self, [(self.binary_command, 'darcs', + 'dev-util/darcs'),]) diff --git a/overlays/git.py b/overlays/git.py new file mode 100644 index 0000000..007e841 --- /dev/null +++ b/overlays/git.py @@ -0,0 +1,63 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN GIT OVERLAY HANDLER +################################################################################# +# File: git.py +# +# Handles git overlays +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel, Stefan Schweizer +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# Stefan Schweizer +''' Git overlay support.''' + +__version__ = "$Id: git.py 146 2006-05-27 09:52:36Z wrobel $" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +from layman.utils import path +from layman.overlays.overlay import Overlay + +#=============================================================================== +# +# Class GitOverlay +# +#------------------------------------------------------------------------------- + +class GitOverlay(Overlay): + ''' Handles git overlays.''' + + type = 'Git' + + binary_command = '/usr/bin/git' + + def add(self, base): + '''Add overlay.''' + + self.supported() + + return self.cmd(self.binary_command + ' clone "' + self.src + '/" "' + + path([base, self.name]) + '"') + + def sync(self, base): + '''Sync overlay.''' + + self.supported() + + return self.cmd('cd "' + path([base, self.name]) + '" && ' + + self.binary_command + ' pull') + + def supported(self): + '''Overlay type supported?''' + + return Overlay.supported(self, [(self.binary_command, 'git', + 'dev-util/git'),]) diff --git a/overlays/mercurial.py b/overlays/mercurial.py new file mode 100644 index 0000000..3def5fc --- /dev/null +++ b/overlays/mercurial.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN MERCURIAL OVERLAY HANDLER +################################################################################# +# File: darcs.py +# +# Handles darcs overlays +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel, Andres Loeh +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# Andres Loeh +# +''' Mercurial overlay support.''' + +__version__ = "$Id: mercurial.py 236 2006-09-05 20:39:37Z wrobel $" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +from layman.utils import path +from layman.overlays.overlay import Overlay + +#=============================================================================== +# +# Class MercurialOverlay +# +#------------------------------------------------------------------------------- + +class MercurialOverlay(Overlay): + ''' Handles mercurial overlays.''' + + type = 'Mercurial' + + binary_command = '/usr/bin/hg' + + def add(self, base): + '''Add overlay.''' + + self.supported() + + return self.cmd(self.binary_command + ' clone "' + self.src + '/" "' + + path([base, self.name]) + '"') + + def sync(self, base): + '''Sync overlay.''' + + self.supported() + + return self.cmd('cd "' + path([base, self.name]) + '" && ' + + self.binary_command + ' pull -u "' + self.src + '"') + + def supported(self): + '''Overlay type supported?''' + + return Overlay.supported(self, [(self.binary_command, 'mercurial', + 'dev-util/mercurial'),]) diff --git a/overlays/overlay.py b/overlays/overlay.py new file mode 100644 index 0000000..b7a006f --- /dev/null +++ b/overlays/overlay.py @@ -0,0 +1,266 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN OVERLAY BASE CLASS +################################################################################# +# File: overlay.py +# +# Base class for the different overlay types. +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# +''' Basic overlay class.''' + +__version__ = "$Id: overlay.py 273 2006-12-30 15:54:50Z wrobel $" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +import re, os, os.path, shutil, popen2 + +from layman.utils import node_to_dict, dict_to_node, path + +from layman.debug import OUT + +#=============================================================================== +# +# Class Overlay +# +#------------------------------------------------------------------------------- + +class Overlay: + ''' Derive the real implementations from this.''' + + type = 'None' + + def __init__(self, xml, ignore = 0, quiet = False): + ''' + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> document = open(here + '/../tests/testfiles/global-overlays.xml').read() + >>> import xml.dom.minidom + >>> document = xml.dom.minidom.parseString(document) + >>> overlays = document.getElementsByTagName('overlay') + >>> a = Overlay(overlays[0]) + >>> a.name + u'wrobel' + >>> a.is_official() + True + >>> a.src + u'https://overlays.gentoo.org/svn/dev/wrobel' + >>> a.contact + u'nobody@gentoo.org' + >>> a.description + u'Test' + >>> a.priority + 10 + >>> b = Overlay(overlays[1]) + >>> b.is_official() + False + ''' + self.quiet = quiet + + self.data = node_to_dict(xml) + + if '&name' in self.data.keys(): + self.name = self.data['&name'] + else: + raise Exception('Overlay is missing a "name" attribute!') + + if '&src' in self.data.keys(): + self.src = self.data['&src'] + else: + raise Exception('Overlay "' + self.name + '" is missing a "src" ' + 'attribute!') + + if '&contact' in self.data.keys(): + self.contact = self.data['&contact'] + else: + self.contact = '' + if not ignore: + raise Exception('Overlay "' + self.name + '" is missing a ' + '"contact" attribute!') + elif ignore == 1: + OUT.warn('Overlay "' + self.name + '" is missing a ' + '"contact" attribute!', 4) + + if '1' in self.data.keys(): + self.description = self.data['1']['@'].strip() + else: + self.description = '' + if not ignore: + raise Exception('Overlay "' + self.name + '" is missing a ' + '"description" entry!') + elif ignore == 1: + OUT.warn('Overlay "' + self.name + '" is missing a ' + '"description" entry!', 4) + + if '&status' in self.data.keys(): + self.status = self.data['&status'] + else: + self.status = '' + + if '&priority' in self.data.keys(): + self.priority = int(self.data['&priority']) + else: + self.priority = 50 + + def to_minidom(self, document): + '''Convert to xml.''' + + return dict_to_node(self.data, document, 'overlay') + + def add(self, base): + '''Add the overlay.''' + + mdir = path([base, self.name]) + + if os.path.exists(mdir): + raise Exception('Directory ' + mdir + ' already exists. Will not ov' + 'erwrite its contents!') + + os.makedirs(mdir) + + def sync(self, base): + '''Sync the overlay.''' + pass + + def delete(self, base): + '''Delete the overlay.''' + mdir = path([base, self.name]) + + if not os.path.exists(mdir): + raise Exception('Directory ' + mdir + ' does not exist. Cannot remo' + 've the overlay!') + + shutil.rmtree(mdir) + + def cmd(self, command): + '''Run a command.''' + + OUT.info('Running command "' + command + '"...', 2) + + if not self.quiet: + return os.system(command) + else: + cmd = popen2.Popen4(command) + cmd.fromchild.readlines() + result = cmd.wait() + cmd.fromchild.readlines() + cmd.fromchild.close() + cmd.tochild.close() + return result + + def __str__(self): + ''' + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> document = open(here + '/../tests/testfiles/global-overlays.xml').read() + >>> import xml.dom.minidom + >>> document = xml.dom.minidom.parseString(document) + >>> overlays = document.getElementsByTagName('overlay') + >>> a = Overlay(overlays[0]) + >>> print str(a) + wrobel + ~~~~~~ + Source : https://overlays.gentoo.org/svn/dev/wrobel + Contact : nobody@gentoo.org + Type : None; Priority: 10 + + Description: + Test + + ''' + + result = '' + + result += self.name + '\n' + (len(self.name) * '~') + + result += '\nSource : ' + self.src + result += '\nContact : ' + self.contact + result += '\nType : ' + self.type + result += '; Priority: ' + str(self.priority) + '\n' + + description = self.description + description = re.compile(' +').sub(' ', description) + description = re.compile('\n ').sub('\n', description) + result += '\nDescription:' + result += '\n '.join(('\n' + description).split('\n')) + result += '\n' + + if '1' in self.data.keys(): + link = self.data['1']['@'].strip() + link = re.compile(' +').sub(' ', link) + link = re.compile('\n ').sub('\n', link) + result += '\nLink:\n' + result += '\n '.join(('\n' + link).split('\n')) + result += '\n' + + return result + + def short_list(self): + ''' + >>> here = os.path.dirname(os.path.realpath(__file__)) + >>> document = open(here + '/../tests/testfiles/global-overlays.xml').read() + >>> import xml.dom.minidom + >>> document = xml.dom.minidom.parseString(document) + >>> overlays = document.getElementsByTagName('overlay') + >>> a = Overlay(overlays[0]) + >>> print a.short_list() + wrobel [None ] (source: https://overlays.gentoo.or...) + ''' + + def pad(string, length): + '''Pad a string with spaces.''' + if len(string) <= length: + return string + ' ' * (length - len(string)) + else: + return string[:length - 3] + '...' + + name = pad(self.name, 25) + mtype = ' [' + pad(self.type, 10) + ']' + source = ' (source: ' + pad(self.src, 29) + ')' + + return name + mtype + source + + def supported(self, binaries = []): + '''Is the overlay type supported?''' + + if binaries: + for mpath, mtype, package in binaries: + if not os.path.exists(mpath): + raise Exception('Binary ' + mpath + ' seems to be missing!' + ' Overlay type "' + mtype + '" not support' + 'ed. Did you emerge ' + package + '?') + + return True + + def is_supported(self): + '''Is the overlay type supported?''' + + try: + self.supported() + return True + except Exception, error: + return False + + def is_official(self): + '''Is the overlay official?''' + + return self.status == 'official' + +#================================================================================ +# +# Testing +# +#-------------------------------------------------------------------------------- + +if __name__ == '__main__': + import doctest, sys + doctest.testmod(sys.modules[__name__]) diff --git a/overlays/rsync.py b/overlays/rsync.py new file mode 100644 index 0000000..e2483ad --- /dev/null +++ b/overlays/rsync.py @@ -0,0 +1,68 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN RSYNC OVERLAY HANDLER +################################################################################# +# File: rsync.py +# +# Handles rsync overlays +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# +''' Rsync overlay support.''' + +__version__ = "$Id: rsync.py 236 2006-09-05 20:39:37Z wrobel $" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +from layman.utils import path +from layman.overlays.overlay import Overlay + +#=============================================================================== +# +# Class RsyncOverlay +# +#------------------------------------------------------------------------------- + +class RsyncOverlay(Overlay): + ''' Handles rsync overlays.''' + + type = 'Rsync' + + binary = '/usr/bin/rsync' + + base = binary + ' -rlptDvz --progress --delete --delete-after ' + \ + '--timeout=180 --exclude="distfiles/*" --exclude="local/*" ' + \ + '--exclude="packages/*" ' + + def add(self, base): + '''Add overlay.''' + + self.supported() + + Overlay.add(self, base) + + return self.sync(base) + + def sync(self, base): + '''Sync overlay.''' + + self.supported() + + return self.cmd(self.base + '"' + self.src + '/*" "' + + path([base, self.name]) + '"') + + def supported(self): + '''Overlay type supported?''' + + return Overlay.supported(self, [(self.binary, 'rsync', + 'net-misc/rsync'),]) diff --git a/overlays/svn.py b/overlays/svn.py new file mode 100644 index 0000000..5086448 --- /dev/null +++ b/overlays/svn.py @@ -0,0 +1,65 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN SVN OVERLAY HANDLER +################################################################################# +# File: svn.py +# +# Handles subversion overlays +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# +''' Subversion overlay support.''' + +__version__ = "$Id: svn.py 236 2006-09-05 20:39:37Z wrobel $" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +from layman.utils import path +from layman.overlays.overlay import Overlay + +#=============================================================================== +# +# Class SvnOverlay +# +#------------------------------------------------------------------------------- + +class SvnOverlay(Overlay): + ''' Handles subversion overlays.''' + + type = 'Subversion' + + binary = '/usr/bin/svn' + + def add(self, base): + '''Add overlay.''' + + self.supported() + + Overlay.add(self, base) + + return self.cmd(self.binary + ' co "' + self.src + '/" "' + + path([base, self.name]) + '"') + + def sync(self, base): + '''Sync overlay.''' + + self.supported() + + return self.cmd(self.binary + ' update "' + path([base, self.name]) + + '"') + + def supported(self): + '''Overlay type supported?''' + + return Overlay.supported(self, [(self.binary, 'svn', + 'dev-util/subversion'),]) diff --git a/overlays/tar.py b/overlays/tar.py new file mode 100644 index 0000000..24a9b91 --- /dev/null +++ b/overlays/tar.py @@ -0,0 +1,189 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN TAR OVERLAY HANDLER +################################################################################# +# File: tar.py +# +# Handles tar overlays +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# +''' Tar overlay support.''' + +__version__ = "$Id: tar.py 310 2007-04-09 16:30:40Z wrobel $" + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +import os, os.path, sys, urllib2, shutil + +from layman.utils import path +from layman.overlays.overlay import Overlay + +#=============================================================================== +# +# Class TarOverlay +# +#------------------------------------------------------------------------------- + +class TarOverlay(Overlay): + ''' Handles tar overlays. + + A dummy tar handler that overwrites the __init__ method + so that we don't need to provide xml input: + + >>> from layman.debug import OUT + >>> class DummyTar(TarOverlay): + ... def __init__(self): + ... self.name = 'dummy' + ... here = os.path.dirname(os.path.realpath(__file__)) + ... self.src = 'file://' + here + '/../tests/testfiles/layman-test.tar.bz2' + ... self.subpath = 'layman-test' + ... self.format = 'bz2' + ... self.quiet = False + >>> testdir = os.tmpnam() + >>> os.mkdir(testdir) + >>> a = DummyTar() + >>> OUT.color_off() + >>> a.add(testdir) #doctest: +ELLIPSIS + * Running command "/bin/tar -v -x -j -f... + >>> sorted(os.listdir(testdir + '/dummy')) + ['app-admin', 'app-portage'] + >>> shutil.rmtree(testdir) + ''' + + type = 'Tar' + + binary = '/bin/tar' + + def __init__(self, xml, ignore = 0, quiet = False): + + Overlay.__init__(self, xml, ignore) + + if '&format' in self.data.keys(): + self.format = self.data['&format'] + else: + self.format = '' + + if '&subpath' in self.data.keys(): + self.subpath = self.data['&subpath'] + else: + self.subpath = '' + + if '&category' in self.data.keys(): + if self.subpath: + raise Exception('Cannot use "category" and "subpath" at the same' + ' time!') + + self.category = self.data['&category'] + else: + self.category = '' + + def add(self, base): + '''Add overlay.''' + + self.supported() + + mdir = path([base, self.name]) + + if os.path.exists(mdir): + raise Exception('Directory ' + mdir + ' already exists. Will not ov' + 'erwrite its contents!') + + if self.format == 'bz2' or (not self.format and self.src[-3:] == 'bz2'): + ext = 'bz2' + opt = '-j' + elif self.format == 'gz' or (not self.format and self.src[-2:] == 'gz'): + ext = 'gz' + opt = '-z' + else: + raise Exception('Unsupported file format!') + + try: + + tar = urllib2.urlopen(self.src).read() + + except Exception, error: + raise Exception('Failed to fetch the tar package from: ' + + self.src + '\nError was:' + str(error)) + + pkg = path([base, self.name + '.tar.' + ext]) + + try: + + out_file = open(pkg, 'w') + out_file.write(tar) + out_file.close() + + except Exception, error: + raise Exception('Failed to store tar package in ' + + pkg + '\nError was:' + str(error)) + + if self.subpath: + target = path([base, 'tmp']) + else: + if self.category: + target = mdir + '/' + self.category + else: + target = mdir + + os.makedirs(target) + + result = self.cmd(self.binary + ' -v -x ' + opt + ' -f "' + pkg + + '" -C "' + target + '"') + + if self.subpath: + source = target + '/' + self.subpath + if os.path.exists(source): + try: + os.rename(source, mdir) + except Exception, error: + raise Exception('Failed to rename tar subdirectory ' + + source + ' to ' + mdir + '\nError was:' + + str(error)) + else: + raise Exception('Given subpath "' + source + '" does not exist ' + ' in the tar package!') + try: + shutil.rmtree(target) + except Exception, error: + raise Exception('Failed to remove unnecessary tar structure "' + + target + '"\nError was:' + str(error)) + + os.unlink(pkg) + + return result + + def sync(self, base): + '''Sync overlay.''' + + self.supported() + + self.delete(base) + + self.add(base) + + def supported(self): + '''Overlay type supported?''' + + return Overlay.supported(self, [(self.binary, 'tar', 'app-arch/tar'), ]) + +if __name__ == '__main__': + import doctest + + # Ignore warnings here. We are just testing + from warnings import filterwarnings, resetwarnings + filterwarnings('ignore') + + doctest.testmod(sys.modules[__name__]) + + resetwarnings() diff --git a/tests/dtest.py b/tests/dtest.py new file mode 100644 index 0000000..8f65af1 --- /dev/null +++ b/tests/dtest.py @@ -0,0 +1,90 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN DOCTEST AGGREGATOR +################################################################################# +# File: dtest.py +# +# Combines the doctests that are available for the different modules +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# +'''Aggregates doctests from all modules that provide such tests.''' + +__version__ = '$Id: dtest.py 237 2006-09-05 21:18:54Z wrobel $' + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +import unittest, doctest, sys + +# On module creation: + +# 1.) Check header section (copyright notice) +# 2.) Add module doc string +# 3.) Add version string +# 4.) Add testing handler at bottom of module +# 5.) Add module into tests/dtest.py. Check that tests run through +# 6.) Run pylint over the code. Fix any reasonable complaints. +# 7.) Whitespace clean the buffer. +# 8.) Add svn:keywords "Id" to file. + +# On module change: + +# 1.) Check header section (copyright notice) +# 5.) Check that tests run through +# 6.) Run pylint over the code. Fix any reasonable complaints. +# 7.) Whitespace clean the buffer. + +# clean modules : CT +# not yet clean : UT +# clean but no testing : CN +# unclean but no testing: UN + +import layman.action #CT +import layman.config #CT +import layman.db #CT +import layman.overlay #CT +import layman.utils #CT +import layman.overlays.overlay #CT +import layman.overlays.tar #CT + +#=============================================================================== +# +# Test Suite +# +#------------------------------------------------------------------------------- + +def test_suite(): + return unittest.TestSuite(( + doctest.DocTestSuite(layman.action), + doctest.DocTestSuite(layman.config), + doctest.DocTestSuite(layman.db), + doctest.DocTestSuite(layman.overlay), + doctest.DocTestSuite(layman.utils), + doctest.DocTestSuite(layman.overlays.overlay), + doctest.DocTestSuite(layman.overlays.tar), + )) + +#=============================================================================== +# +# Run Testing +# +#------------------------------------------------------------------------------- + +if __name__ == '__main__': + # Ignore warnings here. We are just testing + from warnings import filterwarnings, resetwarnings + filterwarnings('ignore') + + unittest.main(defaultTest='test_suite') + + resetwarnings() diff --git a/tests/testfiles/global-overlays.xml b/tests/testfiles/global-overlays.xml new file mode 100644 index 0000000..d770692 --- /dev/null +++ b/tests/testfiles/global-overlays.xml @@ -0,0 +1,30 @@ + + + + + + + Test + + + + + + + + A collection of ebuilds from Gunnar Wrobel [wrobel@gentoo.org]. + + + + + diff --git a/tests/testfiles/layman-test.tar.bz2 b/tests/testfiles/layman-test.tar.bz2 new file mode 100644 index 0000000..85ee7fd Binary files /dev/null and b/tests/testfiles/layman-test.tar.bz2 differ diff --git a/tests/testfiles/make.conf b/tests/testfiles/make.conf new file mode 100644 index 0000000..d32dd16 --- /dev/null +++ b/tests/testfiles/make.conf @@ -0,0 +1,345 @@ +# Copyright 1999-2004 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: /var/cvsroot/gentoo-src/portage/cnf/make.conf.x86,v 1.5.2.5 2005/04/13 15:28:38 jstubbs Exp $ +# Contains local system settings for Portage system + +# Please review 'man make.conf' for more information. + +# Build-time functionality +# ======================== +# +# The USE variable is used to enable optional build-time functionality. For +# example, quite a few packages have optional X, gtk or GNOME functionality +# that can only be enabled or disabled at compile-time. Gentoo Linux has a +# very extensive set of USE variables described in our USE variable HOWTO at +# http://www.gentoo.org/doc/en/handbook/handbook-x86.xml?part=2&chap=1 +# +# The available list of use flags with descriptions is in your portage tree. +# Use 'less' to view them: --> less /usr/portage/profiles/use.desc <-- +# +# 'ufed' is an ncurses/dialog interface available in portage to make handling +# useflags for you. 'emerge app-portage/ufed' +# +# Example: + +# Use flags will be handled by polymeraZe +USE="-*" + +# Host Setting +# ============ +# +# DO NOT CHANGE THIS SETTING UNLESS YOU ARE USING STAGE1! +# Change this line as appropriate (i686, i586, i486 or i386). +# All modern systems (even Athlons) should use "i686-pc-linux-gnu". +# All K6's are i586. +CHOST="i686-pc-linux-gnu" + +# Host and optimization settings +# ============================== +# +# For optimal performance, enable a CFLAGS setting appropriate for your CPU. +# +# Please note that if you experience strange issues with a package, it may be +# due to gcc's optimizations interacting in a strange way. Please test the +# package (and in some cases the libraries it uses) at default optimizations +# before reporting errors to developers. +# +# -mcpu= means optimize code for the particular type of CPU without +# breaking compatibility with other CPUs. +# +# -march= means to take full advantage of the ABI and instructions +# for the particular CPU; this will break compatibility with older CPUs (for +# example, -march=athlon-xp code will not run on a regular Athlon, and +# -march=i686 code will not run on a Pentium Classic. +# +# CPU types supported in gcc-3.2 and higher: athlon-xp, athlon-mp, +# athlon-tbird, athlon, k6, k6-2, k6-3, i386, i486, i586 (Pentium), i686 +# (PentiumPro), pentium, pentium-mmx, pentiumpro, pentium2 (Celeron), +# pentium3, and pentium4. +# +# Note that Gentoo Linux 1.4 and higher include at least gcc-3.2. +# +# CPU types supported in gcc-2.95*: k6, i386, i486, i586 (Pentium), i686 +# (Pentium Pro), pentium, pentiumpro Gentoo Linux 1.2 and below use gcc-2.95* +# +# CRITICAL WARNINGS: ****************************************************** # +# K6 markings are deceptive. Avoid setting -march for them. See Bug #24379. # +# Pentium-M CPU's should not enable sse2 until at least gcc-3.4. Bug 50616. # +# ************************************************************************* # +# +# Decent examples: +# +#CFLAGS="-mcpu=athlon-xp -O3 -pipe" + +CFLAGS="-march=athlon-xp -O3 -pipe" + + +# If you set a CFLAGS above, then this line will set your default C++ flags to +# the same settings. +CXXFLAGS="${CFLAGS}" + +# Advanced Masking +# ================ +# +# Gentoo is using a new masking system to allow for easier stability testing +# on packages. KEYWORDS are used in ebuilds to mask and unmask packages based +# on the platform they are set for. A special form has been added that +# indicates packages and revisions that are expected to work, but have not yet +# been approved for the stable set. '~arch' is a superset of 'arch' which +# includes the unstable, in testing, packages. Users of the 'x86' architecture +# would add '~x86' to ACCEPT_KEYWORDS to enable unstable/testing packages. +# '~ppc', '~sparc' are the unstable KEYWORDS for their respective platforms. +# +# Please note that this is not for development, alpha, beta, nor cvs release +# packages. "Broken" packages will not be added to testing and should not be +# requested to be added. Alternative routes are available to developers +# for experimental packages, and it is at their discretion to use them. +# +# DO NOT PUT ANYTHING BUT YOUR SPECIFIC ~ARCHITECTURE IN THE LIST. +# IF YOU ARE UNSURE OF YOUR ARCH, OR THE IMPLICATIONS, DO NOT MODIFY THIS. +# + +ACCEPT_KEYWORDS="x86" + + +# Portage Directories +# =================== +# +# Each of these settings controls an aspect of portage's storage and file +# system usage. If you change any of these, be sure it is available when +# you try to use portage. *** DO NOT INCLUDE A TRAILING "/" *** +# +# PORTAGE_TMPDIR is the location portage will use for compilations and +# temporary storage of data. This can get VERY large depending upon +# the application being installed. +PORTAGE_TMPDIR=/var/tmp +# +# PORTDIR is the location of the portage tree. This is the repository +# for all profile information as well as all ebuilds. If you change +# this, you must update your /etc/make.profile symlink accordingly. +PORTDIR=/usr/portage +# +# DISTDIR is where all of the source code tarballs will be placed for +# emerges. The source code is maintained here unless you delete +# it. The entire repository of tarballs for gentoo is 9G. This is +# considerably more than any user will ever download. 2-3G is +# a large DISTDIR. +DISTDIR=/usr/distfiles +# +# PKGDIR is the location of binary packages that you can have created +# with '--buildpkg' or '-b' while emerging a package. This can get +# upto several hundred megs, or even a few gigs. +#PKGDIR=${PORTDIR}/packages +# +# PORT_LOGDIR is the location where portage will store all the logs it +# creates from each individual merge. They are stored as NNNN-$PF.log +# in the directory specified. This is disabled until you enable it by +# providing a directory. Permissions will be modified as needed IF the +# directory exists, otherwise logging will be disabled. NNNN is the +# increment at the time the log is created. Logs are thus sequential. +PORT_LOGDIR=/var/log/services/portage.d +# +# PORTDIR_OVERLAY is a directory where local ebuilds may be stored without +# concern that they will be deleted by rsync updates. Default is not +# defined. +PORTDIR_OVERLAY=" +/usr/portage/local/layman/wrobel-stable +$PORTDIR_OVERLAY +/usr/portage/local/ebuilds/testing +/usr/portage/local/ebuilds/stable +/usr/portage/local/kolab2 +/usr/portage/local/gentoo-webapps-overlay/experimental +/usr/portage/local/gentoo-webapps-overlay/production-ready" + +# Fetching files +# ============== +# +# If you need to set a proxy for wget or lukemftp, add the appropriate "export +# ftp_proxy=" and "export http_proxy=" lines to /etc/profile if +# all users on your system should use them. +# +# Portage uses wget by default. Here are some settings for some alternate +# downloaders -- note that you need to merge these programs first before they +# will be available. +# +# Default fetch command (5 tries, passive ftp for firewall compatibility) +#FETCHCOMMAND="/usr/bin/wget -t 5 --passive-ftp \${URI} -P \${DISTDIR}" +#RESUMECOMMAND="/usr/bin/wget -c -t 5 --passive-ftp \${URI} -P \${DISTDIR}" +# +# Using wget, ratelimiting downloads +#FETCHCOMMAND="/usr/bin/wget -t 5 --passive-ftp --limit-rate=200k \${URI} -P \${DISTDIR}" +#RESUMECOMMAND="/usr/bin/wget -c -t 5 --passive-ftp --limit-rate=200k \${URI} -P \${DISTDIR}" +# +# Lukemftp (BSD ftp): +#FETCHCOMMAND="/usr/bin/lukemftp -s -a -o \${DISTDIR}/\${FILE} \${URI}" +#RESUMECOMMAND="/usr/bin/lukemftp -s -a -R -o \${DISTDIR}/\${FILE} \${URI}" +# + +FETCHCOMMAND="/usr/bin/getdelta.sh \${URI}" + + +# Portage uses GENTOO_MIRRORS to specify mirrors to use for source retrieval. +# The list is a space separated list which is read left to right. If you use +# another mirror we highly recommend leaving the default mirror at the end of +# the list so that portage will fall back to it if the files cannot be found +# on your specified mirror. We _HIGHLY_ recommend that you change this setting +# to a nearby mirror by merging and using the 'mirrorselect' tool. + +GENTOO_MIRRORS="http://pandemonium.tiscali.de/pub/gentoo/ ftp://pandemonium.tiscali.de/pub/gentoo/ ftp://ftp-stud.fht-esslingen.de/pub/Mirrors/gentoo/ http://mir.zyrianes.net/gentoo/ http://ftp.snt.utwente.nl/pub/os/linux/gentoo http://distfiles.gentoo.org http://www.ibiblio.org/pub/Linux/distributions/gentoo" + +# +# Portage uses PORTAGE_BINHOST to specify mirrors for prebuilt-binary packages. +# The list is a single entry specifying the full address of the directory +# serving the tbz2's for your system. Running emerge with either '--getbinpkg' +# or '--getbinpkgonly' will cause portage to retrieve the metadata from all +# packages in the directory specified, and use that data to determine what will +# be downloaded and merged. '-g' or '-gK' are the recommend parameters. Please +# consult the man pages and 'emerge --help' for more information. For FTP, the +# default connection is passive -- If you require an active connection, affix +# an asterisk (*) to the end of the host:port string before the path. +#PORTAGE_BINHOST="http://grp.mirror.site/gentoo/grp/1.4/i686/athlon-xp/" +# This ftp connection is passive ftp. +#PORTAGE_BINHOST="ftp://login:pass@grp.mirror.site/pub/grp/i686/athlon-xp/" +# This ftp connection is active ftp. +#PORTAGE_BINHOST="ftp://login:pass@grp.mirror.site:21*/pub/grp/i686/athlon-xp/" + +# Synchronizing Portage +# ===================== +# +# Each of these settings affects how Gentoo synchronizes your Portage tree. +# Synchronization is handled by rsync and these settings allow some control +# over how it is done. +# +# +# SYNC is the server used by rsync to retrieve a localized rsync mirror +# rotation. This allows you to select servers that are geographically +# close to you, yet still distribute the load over a number of servers. +# Please do not single out specific rsync mirrors. Doing so places undue +# stress on particular mirrors. Instead you may use one of the following +# continent specific rotations: +# +# Default: "rsync://rsync.gentoo.org/gentoo-portage" +# North America: "rsync://rsync.namerica.gentoo.org/gentoo-portage" +# South America: "rsync://rsync.samerica.gentoo.org/gentoo-portage" +# Europe: "rsync://rsync.europe.gentoo.org/gentoo-portage" +# Asia: "rsync://rsync.asia.gentoo.org/gentoo-portage" +# Australia: "rsync://rsync.au.gentoo.org/gentoo-portage" + +SYNC="rsync://rsync.europe.gentoo.org/gentoo-portage" + +# +# RSYNC_RETRIES sets the number of times portage will attempt to retrieve +# a current portage tree before it exits with an error. This allows +# for a more successful retrieval without user intervention most times. +#RSYNC_RETRIES="3" +# +# RSYNC_TIMEOUT sets the length of time rsync will wait before it times out +# on a connection. Most users will benefit from this setting as it will +# reduce the amount of 'dead air' they experience when they run across +# the occasional, unreachable mirror. Dialup users might want to set this +# value up around the 300 second mark. +#RSYNC_TIMEOUT=180 + +# Advanced Features +# ================= +# +# MAKEOPTS provides extra options that may be passed to 'make' when a +# program is compiled. Presently the only use is for specifying +# the number of parallel makes (-j) to perform. The suggested number +# for parallel makes is CPUs+1. +MAKEOPTS="-j2" +# +# PORTAGE_NICENESS provides a default increment to emerge's niceness level. +# Note: This is an increment. Running emerge in a niced environment will +# reduce it further. Default is unset. +PORTAGE_NICENESS=3 +# +# AUTOCLEAN enables portage to automatically clean out older or overlapping +# packages from the system after every successful merge. This is the +# same as running 'emerge -c' after every merge. Set with: "yes" or "no". +# This does not affect the unpacked source. See 'noclean' below. +AUTOCLEAN="yes" +# +# PORTAGE_TMPFS is a location where portage may create temporary files. +# If specified, portage will use this directory whenever possible +# for all rapid operations such as lockfiles and transient data. +# It is _highly_ recommended that this be a tmpfs or ramdisk. Do not +# set this to anything that does not give a significant performance +# enhancement and proper FS compliance for locks and read/write. +# /dev/shm is a glibc mandated tmpfs, and should be a reasonable +# setting for all linux kernel+glibc based systems. +#PORTAGE_TMPFS="/dev/shm" +# +# FEATURES are settings that affect the functionality of portage. Most of +# these settings are for developer use, but some are available to non- +# developers as well. +# +# 'autoaddcvs' causes portage to automatically try to add files to cvs +# that will have to be added later. Done at generation times +# and only has an effect when 'cvs' is also set. +# 'buildpkg' causes binary packages to be created of all packages that +# are being merged. +# 'ccache' enables ccache support via CC. +# 'collision-protect' +# prevents packages from overwriting files that are owned by +# another package or by no package at all. +# 'cvs' causes portage to enable all cvs features (commits, adds), +# and to apply all USE flags in SRC_URI for digests -- for +# developers only. +# 'digest' causes digests to be generated for all packages being merged. +# 'distcc' enables distcc support via CC. +# 'distlocks' enables distfiles locking using fcntl or hardlinks. This +# is enabled by default. Tools exist to help clean the locks +# after crashes: /usr/lib/portage/bin/clean_locks. +# 'fixpackages' allows portage to fix binary packages that are stored in +# PKGDIR. This can consume a lot of time. 'fixpackages' is +# also a script that can be run at any given time to force +# the same actions. +# 'gpg' enables basic verification of Manifest files using gpg. +# This features is UNDER DEVELOPMENT and reacts to features +# of strict and severe. Heavy use of gpg sigs is coming. +# 'keeptemp' prevents the clean phase from deleting the temp files ($T) +# from a merge. +# 'keepwork' prevents the clean phase from deleting the WORKDIR. +# 'maketest' causes ebuilds to perform testing phases if they are capable +# of it. Some packages support this automaticaly via makefiles. +# 'noauto' causes ebuild to perform only the action requested and +# not any other required actions like clean or unpack -- for +# debugging purposes only. +# 'noclean' prevents portage from removing the source and temporary files +# after a merge -- for debugging purposes only. +# 'nostrip' prevents the stripping of binaries. +# 'notitles' disables xterm titlebar updates (which contain status info). +# 'sandbox' enables sandboxing when running emerge and ebuild. +# 'strict' causes portage to react strongly to conditions that are +# potentially dangerous, like missing/incorrect Manifest files. +# 'userpriv' allows portage to drop root privileges while it is compiling, +# as a security measure. As a side effect this can remove +# sandbox access violations for users. +# 'usersandbox' enables sandboxing while portage is running under userpriv. +#FEATURES="sandbox buildpkg ccache distcc userpriv usersandbox notitles noclean noauto cvs keeptemp keepwork autoaddcvs" +FEATURES="sandbox ccache userprivs distlocks cvs" +# +# CCACHE_SIZE sets the space use limitations for ccache. The default size is +# 2G, and will be set if not defined otherwise and ccache is in features. +# Portage will set the default ccache dir if it is not present in the +# user's environment, for userpriv it sets: ${PORTAGE_TMPDIR}/ccache +# (/var/tmp/ccache), and for regular use the default is /root/.ccache. +# Sizes are specified with 'G' 'M' or 'K'. +# '2G' for 2 gigabytes, '2048M' for 2048 megabytes (same as 2G). +CCACHE_SIZE="1G" +# +# DISTCC_DIR sets the temporary space used by distcc. +#DISTCC_DIR="${PORTAGE_TMPDIR}/.distcc" +# +# RSYNC_EXCLUDEFROM is a file that portage will pass to rsync when it updates +# the portage tree. Specific chunks of the tree may be excluded from +# consideration. This may cause dependency failures if you are not careful. +# The file format is one pattern per line, blanks and ';' or '#' lines are +# comments. See 'man rsync' for more details on the exclude-from format. +#RSYNC_EXCLUDEFROM=/etc/portage/rsync_excludes +EBEEP_IGNORE=yes + +CONFIG_PROTECT_MASK="/usr/X11R6/lib/X11" + + diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..239f1b3 --- /dev/null +++ b/utils.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# POLYMERAZE XML UTILITIES +################################################################################# +# File: xml.py +# +# Utilities to deal with xml nodes. +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# + +'''Utility functions to deal with xml nodes. ''' + +__version__ = '$Id: utils.py 236 2006-09-05 20:39:37Z wrobel $' + +#=============================================================================== +# +# Dependencies +# +#------------------------------------------------------------------------------- + +import types, re + +#=============================================================================== +# +# Helper functions +# +#------------------------------------------------------------------------------- + +def node_to_text(node): + ''' + Reduces an xml node to its text elements. The function does not + collect the text nodes recursively. + + >>> import xml.dom.minidom + >>> imp = xml.dom.minidom.getDOMImplementation() + >>> doc = imp.createDocument('test', 'root', None) + >>> root = doc.childNodes[0] + >>> node = doc.createTextNode('text') + >>> a = root.appendChild(node) + >>> node = doc.createElement('text') + >>> node2 = doc.createTextNode('text') + >>> a = node.appendChild(node2) + >>> a = root.appendChild(node) + >>> node = doc.createTextNode('text') + >>> a = root.appendChild(node) + >>> doc.toprettyxml('', '') #doctest: +ELLIPSIS + '...texttexttext' + + >>> node_to_text(root) + 'texttext' + + ''' + text = '' + + for child in node.childNodes: + if child.nodeType == child.TEXT_NODE: + text = text + child.data + + return text + +def node_to_dict(node): + ''' Converts a xml node to a dictionary. The function collects the + nodes recursively. Attributes will be prepended with '&', child + nodes will be surrounded with tags. An index will be appended + since several child nodes with the same tag may exist. Text + elements will be collapsed and stored in a n entry prepended with + '@'. Comments will be ignored. + + >>> import xml.dom.minidom + >>> imp = xml.dom.minidom.getDOMImplementation() + >>> doc = imp.createDocument('test', 'root', None) + >>> root = doc.childNodes[0] + >>> node = doc.createTextNode('text') + >>> a = root.appendChild(node) + >>> node = doc.createElement('text') + >>> node2 = doc.createTextNode('text') + >>> comm = doc.createComment('comment') + >>> attr = doc.createAttribute('&attr') + >>> a = node.appendChild(node2) + >>> a = root.appendChild(comm) + >>> node.setAttributeNode(attr) + >>> node.setAttribute('&attr','test') + >>> a = root.appendChild(node) + >>> node3 = doc.createElement('text') + >>> a = root.appendChild(node3) + >>> node = doc.createTextNode('text') + >>> a = root.appendChild(node) + >>> doc.toprettyxml('', '') #doctest: +ELLIPSIS + '...texttexttext' + + >>> node_to_dict(root) + {'1': {'@': 'text', '&&attr': 'test'}, '2': {'@': ''}, '@': 'texttext'} + + ''' + result = {} + + # Map the attributes + for index in range(0, node.attributes.length): + attr = node.attributes.item(index) + result['&' + attr.name] = attr.nodeValue + + text = '' + + # Map the nodes + for child in node.childNodes: + if child.nodeType == child.TEXT_NODE: + text = text + child.data + if child.nodeType == child.ELEMENT_NODE: + index = 1 + while ('<' + child.tagName + '>' + str(index)) in result.keys(): + index += 1 + result['<' + child.tagName + '>' + str(index)] = node_to_dict(child) + + result['@'] = text + + return result + +def dict_to_node(data, document, root_name): + ''' Reverts the node_to_dict operation. + + >>> import xml.dom.minidom + >>> imp = xml.dom.minidom.getDOMImplementation() + >>> doc = imp.createDocument('test', 'root', None) + >>> a = {'1': {'@': 'text', '&&attr': 'test'}, '2': {'@': ''}, '@': 'texttext'} + >>> doc.childNodes[0] = dict_to_node(a, doc, 'root') + >>> doc.toprettyxml('', '') #doctest: +ELLIPSIS + '...texttexttext' + + ''' + node = document.createElement(root_name) + + for i, j in data.items(): + + if i[0] == '&': + attr = document.createAttribute(i[1:]) + node.setAttributeNode(attr) + node.setAttribute(i[1:], j) + if i[0] == '<': + k = i[1:] + while k[-1] in '0123456789': + k = k[:-1] + child = dict_to_node(data[i], + document, + k[:-1]) + node.appendChild(child) + if i[0] == '@': + child = document.createTextNode(j) + node.appendChild(child) + + return node + +def path(path_elements): + ''' + Concatenate a path from several elements. + + >>> path([]) + '' + >>> path(['a']) + 'a' + >>> path(['a','b']) + 'a/b' + >>> path(['a/','b']) + 'a/b' + >>> path(['/a/','b']) + '/a/b' + >>> path(['/a/','b/']) + '/a/b' + >>> path(['/a/','b/']) + '/a/b' + >>> path(['/a/','/b/']) + '/a/b' + >>> path(['/a/','/b','c/']) + '/a/b/c' + ''' + pathname = '' + + if type(path_elements) in types.StringTypes: + path_elements = [path_elements] + + # Concatenate elements and seperate with / + for i in path_elements: + pathname += i + '/' + + # Replace multiple consecutive slashes + pathname = re.compile('/+').sub('/', pathname) + + # Remove the final / if there is one + if pathname and pathname[-1] == '/': + pathname = pathname[:-1] + + return pathname + +#=============================================================================== +# +# Testing +# +#------------------------------------------------------------------------------- + +if __name__ == '__main__': + import doctest, sys + doctest.testmod(sys.modules[__name__]) diff --git a/version.py b/version.py new file mode 100644 index 0000000..943522b --- /dev/null +++ b/version.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +################################################################################# +# LAYMAN VERSION +################################################################################# +# File: version.py +# +# Current version number +# +# Copyright: +# (c) 2005 - 2006 Gunnar Wrobel +# Distributed under the terms of the GNU General Public License v2 +# +# Author(s): +# Gunnar Wrobel +# + +__version__ = "$Id: version.py 309 2007-04-09 16:23:38Z wrobel $" + + +VERSION = '1.0.99' + +if __name__ == '__main__': + print VERSION -- cgit v1.2.3-1-g7c22