diff --git a/README b/README index 1914776f..8c149544 100644 --- a/README +++ b/README @@ -1,3 +1,12 @@ PISI - Packages Installed Succesfully as Intented -PISI is a new package manager implemented in python. +PISI is a new package manager implemented in python for the PARDUS +distribution. + +Main features + + - Implemented in python. + - All specification and intermediate data is in an XML format. + - Fast database access implemented with berkeley DB. + - Integrates both low-level and high-level package operations. + diff --git a/TODO b/TODO index 2d54d265..2fe16f9b 100644 --- a/TODO +++ b/TODO @@ -77,29 +77,39 @@ Legend: / multiple package repository (eray) + decide how to implement + support repo order - - support medium types + ? support medium types - internet: http/ftp - local repository - cdrom dvd / UI - - improve interface - - ask alternatives to choose from (eray) - - pyqt UI class to interface + ? pyqt UI class to interface - PISIMAT / pykde GUI (cartman) + + improve interface + + refactor UI, reintroduce base class + + add an ack interface, start cleaning up a bit + + cli + + ask alternatives to choose from (eray) + + eliminate \n's from infos, what's the point? + metaclass coolness for CLI command framework (eray) - / i18n support (those who know how to do it) + / autoxml: automated xml processing + + design + + basic types + / list typ + - class typ + / localtext type / components + requirements - - xml format (baris) (bunlarin class'leri yazilmali) - - query components + - xml format (bunlarin class'leri yazilmali) + - query components: list of components/packages in a component - install/remove components + / i18n support + - configure-pending (eray) + / overhaul installdb (try to merge its use with packagedb) (eray) + upgrade (eray) + upgrade operation + test upgrade op + system-wide upgrade (upgrade-all) komutu - / overhaul installdb (try to merge its use with packagedb) (eray) - - configure-pending (eray) ? ordered configure install thing (eray) - partial caching and automatic resume for file download (meren) - use a separate "partial/" subdir like urpmi @@ -117,11 +127,11 @@ Legend: + MetaData + Files - Index (o kadar onemli degil) - - implement missing unit tests + + implement missing unit tests + sourcedb + package (baris) + configuration file (baris) - - non-interactive use (baris) + ? non-interactive use (baris) + incremental build (eray) + generate binary release number by comparing MD5s (eray) + keep track of successfully completed configure, make, install @@ -133,6 +143,10 @@ Legend: 5. Post Release + - a database of components: faster access + - more support for categories: + - put categories into a database + - fast query for categories / make a package and sourcedb for each repo + packagedb - sourceb diff --git a/pisi-cli b/pisi-cli index 9b6c228d..7779642c 100755 --- a/pisi-cli +++ b/pisi-cli @@ -25,7 +25,7 @@ def handle_exception(exception, value, tb): import pisi.ui from pisi.xmlext import XmlError - ui = pisi.ui.CLI() + ui = pisi.cli.CLI() if exception == exceptions.KeyboardInterrupt: ui.error(_("\nKeyboardInterrupt: Exiting...\n")) diff --git a/pisi/actionsapi/kde.py b/pisi/actionsapi/kde.py index c9742af5..a764fc7a 100644 --- a/pisi/actionsapi/kde.py +++ b/pisi/actionsapi/kde.py @@ -25,7 +25,7 @@ class ConfigureError(pisi.actionsapi.Error): def __init__(self, Exception): ctx.ui.error(Exception) if can_access_file('config.log'): - ctx.ui.error('\n!!! Please attach the config.log to your bug report:\n%s/config.log\n' % os.getcwd()) + ctx.ui.error('\n!!! Please attach the config.log to your bug report:\n%s/config.log' % os.getcwd()) class MakeError(pisi.actionsapi.Error): def __init__(self, Exception): diff --git a/pisi/actionsapi/libtools.py b/pisi/actionsapi/libtools.py index 085411bc..a73f9d57 100644 --- a/pisi/actionsapi/libtools.py +++ b/pisi/actionsapi/libtools.py @@ -35,16 +35,16 @@ def preplib_so(sourceDirectory): pass def gnuconfig_update(): - ''' copy newest config.* onto source's ''' + ''' copy newest config.* onto source\'s ''' for root, dirs, files in os.walk(os.getcwd()): for file in files: if file in ['config.sub', 'config.guess']: copy('/usr/share/gnuconfig/%s' % file, os.path.join(root, file)) - ctx.ui.info('GNU Config Update Finished...\n') + ctx.ui.info('GNU Config Update Finished.') def libtoolize(parameters = ''): if system('/usr/bin/libtoolize %s' % parameters): - raise RunTimeError('!!! Running libtoolize failed...') + raise RunTimeError('Running libtoolize failed...') def gen_usr_ldscript(dynamicLib): diff --git a/pisi/actionsapi/pisitools.py b/pisi/actionsapi/pisitools.py index 09736194..c72fa24e 100644 --- a/pisi/actionsapi/pisitools.py +++ b/pisi/actionsapi/pisitools.py @@ -124,7 +124,7 @@ def doman(*sourceFiles): pageName, pageDirectory = source[:source.rindex('.')], \ source[source.rindex('.')+1:] except ValueError: - ctx.ui.error('\n!!! ActionsAPI [doman]: Wrong man page file...\n') + ctx.ui.error('\n!!! ActionsAPI [doman]: Wrong man page file...') makedirs(manDIR + '/man%s' % pageDirectory) system('install -m0644 %s %s' % (source, manDIR + '/man%s' % pageDirectory)) @@ -185,7 +185,7 @@ def dosym(sourceFile, destinationFile): try: os.symlink(sourceFile, get.installDIR() + destinationFile) except OSError: - ctx.ui.error('\n!!! ActionsAPI [dosym]: File exists...\n') + ctx.ui.error('\n!!! ActionsAPI [dosym]: File exists...') def insinto(destinationDirectory, sourceFile, destinationFile = ''): '''insert a sourceFile into destinationDirectory as a destinationFile with same uid/guid/permissions''' diff --git a/pisi/actionsapi/shelltools.py b/pisi/actionsapi/shelltools.py index dbbcb46f..a568290b 100644 --- a/pisi/actionsapi/shelltools.py +++ b/pisi/actionsapi/shelltools.py @@ -43,7 +43,7 @@ def echo(destionationFile, content): f.write("%s\n" % content) f.close() except IOError: - ctx.ui.error('\n!!! ActionsAPI [echo]: Can\'t append to file...\n') + ctx.ui.error(' ActionsAPI [echo]: Can\'t append to file...') def chmod(sourceFile, mode = 0755): '''change the mode of sourceFile to the mode''' @@ -52,9 +52,9 @@ def chmod(sourceFile, mode = 0755): try: os.chmod(file, mode) except OSError: - ctx.ui.error('\n!!! ActionsAPI [chmod]: Operation not permitted...\n') + ctx.ui.error(' ActionsAPI [chmod]: Operation not permitted...') else: - ctx.ui.error('\n!!! ActionsAPI [chmod]: File doesn\'t exists...\n') + ctx.ui.error(' ActionsAPI [chmod]: File doesn\'t exists...') def chown(sourceFile, uid = 0, gid = 0): '''change the owner and group id of sourceFile to the numeric uid and gid''' @@ -62,16 +62,16 @@ def chown(sourceFile, uid = 0, gid = 0): try: os.chown(sourceFile, uid, gid) except OSError: - ctx.ui.error('\n!!! ActionsAPI [chown]: Operation not permitted...\n') + ctx.ui.error(' ActionsAPI [chown]: Operation not permitted...') else: - ctx.ui.error('\n!!! ActionsAPI [chown]: File doesn\'t exists...\n') + ctx.ui.error(' ActionsAPI [chown]: File doesn\'t exists...') def sym(sourceFile, destinationFile): '''creates symbolic link''' try: os.symlink(sourceFile, destinationFile) except OSError: - ctx.ui.error('\n!!! ActionsAPI [sym]: Permission denied...\n') + ctx.ui.error(' ActionsAPI [sym]: Permission denied...') def unlink(sourceFile): '''remove the file path''' @@ -79,11 +79,11 @@ def unlink(sourceFile): try: os.unlink(sourceFile) except OSError: - ctx.ui.error('\n!!! ActionsAPI [unlink]: Permission denied...\n') + ctx.ui.error(' ActionsAPI [unlink]: Permission denied.') elif isDirectory(sourceFile): pass else: - ctx.ui.error('\n!!! ActionsAPI [unlink]: File doesn\'t exists...\n') + ctx.ui.error(' ActionsAPI [unlink]: File doesn\'t exists.') def unlinkDir(sourceDirectory): '''delete an entire directory tree''' @@ -91,11 +91,11 @@ def unlinkDir(sourceDirectory): try: shutil.rmtree(sourceDirectory) except OSError: - ctx.ui.error('\n!!! ActionsAPI [unlinkDir]: Operation not permitted...\n') + ctx.ui.error(' ActionsAPI [unlinkDir]: Operation not permitted.') elif isFile(sourceDirectory): pass else: - ctx.ui.error('\n!!! ActionsAPI [unlinkDir]: Directory doesn\'t exists...\n') + ctx.ui.error(' ActionsAPI [unlinkDir]: Directory doesn\'t exists.') def move(sourceFile, destinationFile): '''recursively move a sourceFile or directory to destinationFile''' @@ -104,9 +104,9 @@ def move(sourceFile, destinationFile): try: shutil.move(file, destinationFile) except OSError: - ctx.ui.error('\n!!! ActionsAPI [move]: Permission denied...\n') + ctx.ui.error(' ActionsAPI [move]: Permission denied.') else: - ctx.ui.error('\n!!! ActionsAPI [move]: File doesn\'t exists...\n') + ctx.ui.error(' ActionsAPI [move]: File doesn\'t exists.') def copy(sourceFile, destinationFile): '''recursively copy a sourceFile or directory to destinationFile''' @@ -115,9 +115,9 @@ def copy(sourceFile, destinationFile): try: shutil.copy(file, destinationFile) except IOError: - ctx.ui.error('\n!!! ActionsAPI [copy]: Permission denied...\n') + ctx.ui.error(' ActionsAPI [copy]: Permission denied.') else: - ctx.ui.error('\n!!! ActionsAPI [copy]: File doesn\'t exists...\n') + ctx.ui.error(' ActionsAPI [copy]: File doesn\'t exists.') def copytree(source, destination, sym = False): '''recursively copy an entire directory tree rooted at source''' @@ -125,9 +125,9 @@ def copytree(source, destination, sym = False): try: shutil.copytree(source, destination, sym) except OSError: - ctx.ui.error('\n!!! ActionsAPI [copytree]: Permission denied...\n') + ctx.ui.error(' ActionsAPI [copytree]: Permission denied.') else: - ctx.ui.error('\n!!! ActionsAPI [copytree]: Directory doesn\'t exists...\n') + ctx.ui.error(' ActionsAPI [copytree]: Directory doesn\'t exists.') def touch(sourceFile): '''changes the access time of the 'sourceFile', or creates it if it is not exist''' @@ -139,7 +139,7 @@ def touch(sourceFile): f = open(sourceFile, 'w') f.close() except IOError: - ctx.ui.error('\n!!! ActionsAPI [touch]: Permission denied...\n') + ctx.ui.error(' ActionsAPI [touch]: Permission denied.') def cd(directoryName = ''): '''change directory''' @@ -187,12 +187,12 @@ def dirName(sourceFile): def system(command): #FIXME: String formatting command = command.replace(" ", " ") - ctx.ui.debug('executing %s\n' % command) + ctx.ui.debug('executing %s' % command) p = os.popen(command) while 1: line = p.readline() if not line: break - ctx.ui.debug(line) + ctx.ui.debug(line[0:len(line)-1]) return p.close() diff --git a/pisi/api.py b/pisi/api.py index 68374997..aaa2c320 100644 --- a/pisi/api.py +++ b/pisi/api.py @@ -7,9 +7,10 @@ # # Please read the COPYING file. # +# Authors: Eray Ozkural +# Baris Metin -# top level PISI interfaces -# a facade to the entire PISI system +"""Top level PISI interfaces. a facade to the entire PISI system""" import os import sys @@ -29,11 +30,13 @@ import pisi.packagedb as packagedb import pisi.repodb import pisi.installdb from pisi.index import Index +import pisi.cli class Error(pisi.Error): pass + def init(database = True, options = None, ui = None ): """Initialize PiSi subsystem""" @@ -41,11 +44,10 @@ def init(database = True, options = None, ui = None ): ctx.config = pisi.config.Config(options) if ui is None: - import pisi.ui if options: - pisi.context.ui = pisi.ui.CLI(options.debug) + pisi.context.ui = pisi.cli.CLI(options.debug) else: - pisi.context.ui = pisi.ui.CLI() + pisi.context.ui = pisi.cli.CLI() else: pisi.context.ui = ui @@ -58,7 +60,6 @@ def init(database = True, options = None, ui = None ): # import pisi.sourcedb # pisi.sourcedb.init() - def install(packages): """install a list of packages (either files/urls, or names)""" @@ -73,23 +74,23 @@ def install(packages): else: return install_pkg_names(packages) + #FIXME: As Gurer warns, something's fishy with this exception proc. except InstallError, e: - ctx.ui.error("%s\n" % e) + ctx.ui.error("InstallError:%s" % e) except packagedb.Error, e: - ctx.ui.error("PackageDBError: (%s)\n" % e) - ctx.ui.error("Package is not installable.\n") + ctx.ui.error("PackageDBError: (%s)" % e) + ctx.ui.error("Package is not installable.") #except Exception, e: # print e - # ctx.ui.error("Error: %s\n" % e) - + # ctx.ui.error("Error: %s" % e) def install_pkg_files(package_URIs): """install a number of pisi package files""" from package import Package - ctx.ui.debug('A = %s\n' % str(package_URIs)) + ctx.ui.debug('A = %s' % str(package_URIs)) for x in package_URIs: if not x.endswith(ctx.const.package_prefix): @@ -142,7 +143,7 @@ def install_pkg_files(package_URIs): A = d_t.keys() if len(A)==0: - ctx.ui.info('No packages to install.\n') + ctx.ui.info('No packages to install.') return True # try to construct a pisi graph of packages to @@ -186,10 +187,10 @@ def install_pkg_names(A): the repository, trying to perform a minimum number of installs""" - ctx.ui.debug('A = %s\n' % str(A)) + ctx.ui.debug('A = %s' % str(A)) if len(A)==0: - ctx.ui.info('No packages to install.\n') + ctx.ui.info('No packages to install.') return True # try to construct a pisi graph of packages to @@ -241,7 +242,7 @@ def upgrade_pkg_names(A): Ap = [] for x in A: if not ctx.installdb.is_installed(x): - ctx.ui.info('Package %s is not installed.\n' % x) + ctx.ui.info('Package %s is not installed.' % x) continue (version, release, build) = ctx.installdb.get_version(x) pkg = packagedb.get_package(x) @@ -253,15 +254,15 @@ def upgrade_pkg_names(A): else: #ctx.ui.info('Package %s cannot be upgraded. ' % x) ctx.ui.info('Package %s is already at its latest version %s,\ - release %s, build %s.\n' + release %s, build %s.' % (x, pkg.version, pkg.release, pkg.build)) A = Ap if len(A)==0: - ctx.ui.info('No packages to upgrade.\n') + ctx.ui.info('No packages to upgrade.') return True - ctx.ui.debug('A = %s\n' % str(A)) + ctx.ui.debug('A = %s' % str(A)) # try to construct a pisi graph of packages to # install / reinstall @@ -337,11 +338,11 @@ def remove(A): if ctx.installdb.is_installed(x): Ap.append(x) else: - ctx.ui.info('Package %s does not exist. Cannot remove.\n' % x) + ctx.ui.info('Package %s does not exist. Cannot remove.' % x) A = Ap if len(A)==0: - ctx.ui.info('No packages to remove.\n') + ctx.ui.info('No packages to remove.') return True # try to construct a pisi graph of packages to @@ -377,7 +378,7 @@ def remove(A): if ctx.installdb.is_installed(x): operations.remove_single(x) else: - ctx.ui.info('Package %s is not installed. Cannot remove.\n' % x) + ctx.ui.info('Package %s is not installed. Cannot remove.' % x) return True # everything went OK :) @@ -423,11 +424,11 @@ def info_name(package_name): def index(repo_dir = '.'): - ctx.ui.info('* Building index of PISI files under %s\n' % repo_dir) + ctx.ui.info('* Building index of PISI files under %s' % repo_dir) index = Index() index.index(repo_dir) index.write(ctx.const.pisi_index) - ctx.ui.info('* Index file written\n') + ctx.ui.info('* Index file written') def add_repo(name, indexuri): @@ -438,16 +439,16 @@ def remove_repo(name): if ctx.repodb.has_repo(name): ctx.repodb.remove_repo(name) else: - ctx.ui.error('* Repository %s does not exist. Cannot remove.\n' + ctx.ui.error('* Repository %s does not exist. Cannot remove.' % name) def update_repo(repo): - ctx.ui.info('* Updating repository: %s\n' % repo) + ctx.ui.info('* Updating repository: %s' % repo) index = Index() index.read(ctx.repodb.get_repo(repo).indexuri.get_uri(), repo) index.update_db(repo) - ctx.ui.info('* Package database updated.\n') + ctx.ui.info('* Package database updated.') # build functions... @@ -513,7 +514,7 @@ def build_until(pspecfile, state, authInfo=None): pb.compile_action_script() last = pb.get_state() - ctx.ui.info("Last state was %s\n"%last) + ctx.ui.info("Last state was %s"%last) if not last: last = "none" diff --git a/pisi/build.py b/pisi/build.py index d0242321..3b012fd8 100644 --- a/pisi/build.py +++ b/pisi/build.py @@ -71,7 +71,7 @@ def check_path_collision(package, pkgList): # path.pathname: /usr/share/doc if util.subpath(pinfo.pathname, path.pathname): collisions.append(path.pathname) - ctx.ui.error(_('Path %s belongs in multiple packages\n') % + ctx.ui.error(_('Path %s belongs in multiple packages') % path.pathname) return collisions @@ -141,7 +141,7 @@ class PisiBuild: def build(self): """Build the package in one shot.""" - ctx.ui.info(_("Building PISI source package: %s\n") % self.spec.source.name) + ctx.ui.info(_("Building PISI source package: %s") % self.spec.source.name) util.xterm_title(_("Building PISI source package: %s\n") % self.spec.source.name) self.compile_action_script() @@ -178,15 +178,15 @@ class PisiBuild: os.environ.update(evn) def fetch_source_archive(self): - ctx.ui.info(_("Fetching source from: %s\n") % self.spec.source.archiveUri) + ctx.ui.info(_("Fetching source from: %s") % self.spec.source.archiveUri) self.sourceArchive.fetch() - ctx.ui.info(_("Source archive is stored: %s/%s\n") + ctx.ui.info(_("Source archive is stored: %s/%s") %(ctx.config.archives_dir(), self.spec.source.archiveName)) def unpack_source_archive(self): ctx.ui.info(_("Unpacking archive...")) self.sourceArchive.unpack() - ctx.ui.info(_(" unpacked (%s)\n") % self.bctx.pkg_work_dir()) + ctx.ui.info(_(" unpacked (%s)") % self.bctx.pkg_work_dir()) self.set_state("unpack") def run_setup_action(self): @@ -220,10 +220,10 @@ class PisiBuild: buf = open(scriptfile).read() exec compile(buf, "error", "exec") in localSymbols, globalSymbols except IOError, e: - ctx.ui.error(_("Unable to read Action Script (%s): %s\n") %(scriptfile,e)) + ctx.ui.error(_("Unable to read Action Script (%s): %s") %(scriptfile,e)) sys.exit(1) except SyntaxError, e: - ctx.ui.error (_("SyntaxError in Action Script (%s): %s\n") %(scriptfile,e)) + ctx.ui.error (_("SyntaxError in Action Script (%s): %s") %(scriptfile,e)) sys.exit(1) self.actionLocals = localSymbols @@ -427,17 +427,17 @@ class PisiBuild: self.spec.source.version, self.spec.source.release) - ctx.ui.action(_("** Building package %s\n") % package.name); + ctx.ui.action(_("** Building package %s") % package.name); ctx.ui.action(_("Generating %s...") % ctx.const.files_xml) self.gen_files_xml(package) - ctx.ui.info(_(" done.\n")) + ctx.ui.info(_(" done.")) ctx.ui.action(_("Generating %s...") % ctx.const.metadata_xml) self.gen_metadata_xml(package) - ctx.ui.info(_(" done.\n")) + ctx.ui.info(_(" done.")) - ctx.ui.action(_("Creating PISI package %s\n") % name) + ctx.ui.action(_("Creating PISI package %s") % name) pkg = Package(name, 'w') diff --git a/pisi/cli/README b/pisi/cli/README index 665fb4c9..5ad52a09 100644 --- a/pisi/cli/README +++ b/pisi/cli/README @@ -1,6 +1,8 @@ Writing commands: ----------------- +TODO: this is a *bit* out of date now. + Subclass from Command. Be careful not to import PiSi modules before Command is run, e.g. diff --git a/pisi/cli/__init__.py b/pisi/cli/__init__.py index 3758fa3c..507c6eea 100644 --- a/pisi/cli/__init__.py +++ b/pisi/cli/__init__.py @@ -11,10 +11,74 @@ # pisi.cli package version __version__ = "0.1" +import sys + import pisi +from pisi.ui import UI +from pisi.cli.colors import colorize + class Error(pisi.Error): pass + class Exception(pisi.Exception): pass + + +class CLI(UI): + "Command Line Interface" + + def __init__(self, show_debug = False, show_verbose = False): + super(CLI, self).__init__(show_debug, show_verbose) + + def output(self, str): + sys.stdout.write(str) + sys.stdout.flush() + + def info(self, msg, verbose = False): + # TODO: need to look at more kinds of info messages + # let's cheat from KDE :) + if verbose and self.show_verbose: + self.output(msg + '\n') + elif not verbose: + self.output(msg + '\n') + + def warning(self,msg): + self.output(colorize('Warning:' + msg + '\n', 'purple')) + + def error(self,msg): + self.output(colorize('Error:' + msg + '\n', 'red')) + + def action(self,msg): + #TODO: this seems quite redundant? + self.output(colorize(msg + '\n', 'green')) + + def choose(self, msg, opts): + print msg + for i in range(0,len(opts)): + print i + 1, opts(i) + while True: + s = raw_input(msg + colorize('1-%d' % len(opts), 'red')) + try: + opt = int(s) + if 1 <= opt and opt <= len(opts): + return opts(opt-1) + except (Exception,e): + pass + + def confirm(self, msg): + if ctx.config.options and ctx.config.options.yes_all: + return True + while True: + s = raw_input(msg + colorize('(yes/no)', 'red')) + if s.startswith('y') or s.startswith('Y'): + return True + if s.startswith('n') or s.startswith('N'): + return False + + def display_progress(self, pd): + out = '\r%-30.30s %3d%% %12.2f %s' % \ + (pd['filename'], pd['percent'], pd['rate'], pd['symbol']) + self.output(out) + diff --git a/pisi/colors.py b/pisi/cli/colors.py similarity index 100% rename from pisi/colors.py rename to pisi/cli/colors.py diff --git a/pisi/dependency.py b/pisi/dependency.py index bfe4bf39..746738cc 100644 --- a/pisi/dependency.py +++ b/pisi/dependency.py @@ -111,7 +111,7 @@ dependency spec""" def satisfies_dependencies(pkg, deps, sat = installed_satisfies_dep): for dep in deps: if not sat(dep): - ctx.ui.error('Package %s does not satisfy dependency %s\n' % + ctx.ui.error('Package %s does not satisfy dependency %s' % (pkg,dep)) return False return True @@ -124,7 +124,7 @@ def installable(pkg): """calculate if pkg is installable currently which means it has to satisfy both install and runtime dependencies""" if not packagedb.has_package(pkg): - ctx.ui.info("Package " + pkg + " is not present in the package database\n"); + ctx.ui.info("Package " + pkg + " is not present in the package database"); return False elif satisfies_runtime_deps(pkg): return True diff --git a/pisi/fetcher.py b/pisi/fetcher.py index 25176b24..cf17649e 100644 --- a/pisi/fetcher.py +++ b/pisi/fetcher.py @@ -37,7 +37,8 @@ def fetch_url(url, dest, progress=None): fetch.progress = progress fetch.fetch() if progress: - ctx.ui.info('\n') + pass + #ctx.ui.info('\n') class Fetcher: diff --git a/pisi/index.py b/pisi/index.py index b6f961e5..e20e36d7 100644 --- a/pisi/index.py +++ b/pisi/index.py @@ -67,7 +67,7 @@ class Index(XmlFile): for root, dirs, files in os.walk(repo_uri): for fn in files: if fn.endswith(ctx.const.package_prefix): - ctx.ui.info('Adding ' + fn + ' to package index\n') + ctx.ui.info('Adding ' + fn + ' to package index') self.add_package(os.path.join(root, fn), repo_uri) def update_db(self, repo): @@ -93,6 +93,6 @@ class Index(XmlFile): md.package.packageURI = util.removepathprefix(repo_uri, path) # check package semantics if md.has_errors(): - ctx.ui.error('Package ' + md.package.name + ': metadata corrupt\n') + ctx.ui.error('Package ' + md.package.name + ': metadata corrupt') else: self.packages.append(md.package) diff --git a/pisi/install.py b/pisi/install.py index d37d20e1..446fba98 100644 --- a/pisi/install.py +++ b/pisi/install.py @@ -44,7 +44,7 @@ class Installer: def install(self, ask_reinstall = True): "entry point" - ctx.ui.info('Installing %s, version %s, release %s, build %s\n' % + ctx.ui.info('Installing %s, version %s, release %s, build %s' % (self.pkginfo.name, self.pkginfo.version, self.pkginfo.release, self.pkginfo.build)) self.ask_reinstall = ask_reinstall @@ -77,7 +77,7 @@ class Installer: # check dependencies if not dependency.installable(self.pkginfo.name): ctx.ui.error('Dependencies for ' + self.pkginfo.name + - ' not satisfied\n') + ' not satisfied') raise InstallError("Package not installable") def reinstall(self): @@ -108,14 +108,14 @@ class Installer: # is this an upgrade? # determine and report the kind of upgrade: version, release, build if pkg.version > iversion: - ctx.ui.info('Upgrading to new upstream version\n') + ctx.ui.info('Upgrading to new upstream version') upgrade = True elif pkg.release > irelease: - ctx.ui.info('Upgrading to new distribution release\n') + ctx.ui.info('Upgrading to new distribution release') upgrade = True elif ((not ignore_build) and ibuild and pkg.build and pkg.build > ibuild): - ctx.ui.info('Upgrading to new distribution build\n') + ctx.ui.info('Upgrading to new distribution build') upgrade = True # is this a downgrade? confirm this action. @@ -135,7 +135,7 @@ class Installer: def extract_install(self): "unzip package in place" - ctx.ui.info('Extracting files,\n') + ctx.ui.info('Extracting files,') self.package.extract_dir_flat('install', ctx.config.destdir) def store_pisi_files(self): @@ -145,14 +145,14 @@ class Installer: ctx.ui.info('Storing %s, ' % ctx.const.files_xml) self.package.extract_file(ctx.const.files_xml, self.package.pkg_dir()) - ctx.ui.info('%s.\n' % ctx.const.metadata_xml) + ctx.ui.info('%s.' % ctx.const.metadata_xml) self.package.extract_file(ctx.const.metadata_xml, self.package.pkg_dir()) for pcomar in self.metadata.package.providesComar: fpath = os.path.join(ctx.const.comar_dir, pcomar.script) # comar prefix is added to the pkg_dir while extracting comar # script file. so we'll use pkg_dir as destination. - ctx.ui.info('Storing %s\n' % fpath) + ctx.ui.info('Storing %s' % fpath) self.package.extract_file(fpath, self.package.pkg_dir()) def register_comar_scripts(self): @@ -160,7 +160,7 @@ class Installer: for pcomar in self.metadata.package.providesComar: scriptPath = os.path.join(self.package.comar_dir(),pcomar.script) - ctx.ui.info("Registering COMAR script %s\n" % pcomar.script) + ctx.ui.info("Registering COMAR script %s" % pcomar.script) # FIXME: We must check the result of the command (possibly # with id?) if comard: diff --git a/pisi/lockeddbshelve.py b/pisi/lockeddbshelve.py index c4370416..6d4c1832 100644 --- a/pisi/lockeddbshelve.py +++ b/pisi/lockeddbshelve.py @@ -51,7 +51,7 @@ class LockedDBShelf(shelve.DBShelf): fcntl.flock(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError: import sys - pisi.context.ui.error("Another instance of PISI is running. Try later!\n") + pisi.context.ui.error("Another instance of PISI is running. Try later!") sys.exit(1) return self.db.open(filename, dbname, filetype, flags, mode) diff --git a/pisi/operations.py b/pisi/operations.py index 22a0b91b..ca63ad0b 100644 --- a/pisi/operations.py +++ b/pisi/operations.py @@ -7,8 +7,9 @@ # # Please read the COPYING file. # +# -# Package Operations: install/remove/upgrade +"Package Operations: install/remove/upgrade" import os @@ -46,7 +47,6 @@ def remove_single(package_name): if comard: # FIXME: (return value)... comard.remove(package_name) - ctx.ui.info('.\n') def install_single(pkg, upgrade = False): """install a single package from URI or ID""" @@ -81,12 +81,12 @@ def install_single_name(name, upgrade = False): pkg_path = os.path.join(os.path.dirname(repo.indexuri.get_uri()), str(pkg_uri.path())) - ctx.ui.debug("Package URI: %s\n" % pkg_path) + ctx.ui.debug("Package URI: %s" % pkg_path) # Package will handle remote file for us! install_single_file(pkg_path, upgrade) else: - ctx.ui.error("Package %s not found in any active repository.\n" % pkg) + ctx.ui.error("Package %s not found in any active repository." % pkg) # deneme, don't remove ulan class AtomicOperation(object): diff --git a/pisi/packagedb.py b/pisi/packagedb.py index 4c4a6b7f..b3b4a17f 100644 --- a/pisi/packagedb.py +++ b/pisi/packagedb.py @@ -42,7 +42,7 @@ class PackageDB(object): fcntl.flock(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError, e: import sys - ctx.ui.error("PackageDB: %s\n" % e) + ctx.ui.error("PackageDB: %s" % e) sys.exit(1) self.d = shelve.open(self.fname) self.dr = shelve.open(self.fname2) diff --git a/pisi/sourcearchive.py b/pisi/sourcearchive.py index 0e4411b0..90645610 100644 --- a/pisi/sourcearchive.py +++ b/pisi/sourcearchive.py @@ -9,12 +9,11 @@ # # Please read the COPYING file. # +# Authors: Baris Metin +# Eray Ozkural # python standard library -# Authors: Baris Metin - from os.path import join from os import access, R_OK @@ -55,7 +54,7 @@ class SourceArchive: # check hash if util.check_file_hash(self.archiveFile, self.archiveSHA1): if interactive: - ctx.ui.info('%s [cached]\n' % self.archiveName) + ctx.ui.info('%s [cached]' % self.archiveName) return True return False diff --git a/pisi/sourcefetcher.py b/pisi/sourcefetcher.py index 91701a03..d9314651 100644 --- a/pisi/sourcefetcher.py +++ b/pisi/sourcefetcher.py @@ -74,7 +74,7 @@ class SourceFetcher(object): def fetch(self, appendDest=""): from fetcher import fetch_url - ctx.ui.info("Fetching %s\n" % self.url.uri) + ctx.ui.info("Fetching %s" % self.url.uri) dest = join(self.dest, appendDest) fetch_url(self.url, dest) diff --git a/pisi/specfile.py b/pisi/specfile.py index 18c58037..1cad424f 100644 --- a/pisi/specfile.py +++ b/pisi/specfile.py @@ -9,13 +9,19 @@ # # Please read the COPYING file. # - -# Specfile module is our handler for PSPEC files. PSPEC (PISI SPEC) -# files are specification files for PISI source packages. This module -# provides read and write access to PSPEC files. - # Authors: Eray Ozkural -# Baris Metin +# Baris Metin +# History: +# Baris wrote the first version, then Baris and Eray did +# several revisions of it. It was rewritten in accordance +# with Gurer's observations. + + +"""Specfile module is our handler for PSPEC files. PSPEC (PISI SPEC) +files are specification files for PISI source packages. This module +provides read and write access to PSPEC files.""" + # standard python modules from os.path import basename diff --git a/pisi/ui.py b/pisi/ui.py index f97d888d..e29e6bde 100644 --- a/pisi/ui.py +++ b/pisi/ui.py @@ -7,63 +7,18 @@ # # Please read the COPYING file. # - # generic user interface - +# # Authors: Eray Ozkural # Murat Eren import sys -from pisi.colors import colorize +import pisi +import pisi.context as ctx - -class CLI: - "Command Line Interface" - def __init__(self, debuggy = False, verbose = False): - self.show_debug = debuggy - self.show_verbose = verbose - - def set_verbose(self, flag): - self.show_verbose = flag - - def set_debug(self, flag): - self.show_debug = flag - - def info(self, msg, verbose = False): - if verbose and self.show_verbose: - sys.stdout.write(colorize(msg, 'blue')) - elif not verbose: - sys.stdout.write(colorize(msg, 'blue')) - sys.stdout.flush() - - def debug(self, msg): - if self.show_debug: - sys.stdout.write(msg) - sys.stdout.flush() - - def warning(self,msg): - sys.stdout.write(colorize('Warning:' + msg, 'purple')) - sys.stdout.flush() - - def error(self,msg): - sys.stdout.write(colorize('Error:' + msg, 'red')) - sys.stdout.flush() - - def action(self,msg): - sys.stdout.write(colorize(msg, 'green')) - sys.stdout.flush() - - def confirm(self, msg): - from pisi.config import config - if config.options and config.options.yes_all: - return True - while True: - s = raw_input(msg + colorize('(yes/no)', 'red')) - if s.startswith('y') or s.startswith('Y'): - return True - if s.startswith('n') or s.startswith('N'): - return False +class UI(object): + "Abstract class for UI operations, derive from this." class Progress: def __init__(self, totalsize): @@ -81,8 +36,49 @@ class CLI: else: return 0 - def display_progress(self, pd): - out = '\r%-30.30s %3d%% %12.2f %s' % \ - (pd['filename'], pd['percent'], pd['rate'], pd['symbol']) - self.info(out) + def __init__(self, debuggy = False, verbose = False): + self.show_debug = debuggy + self.show_verbose = verbose + def set_verbose(self, flag): + self.show_verbose = flag + + def set_debug(self, flag): + self.show_debug = flag + + def info(self, msg, verbose = False): + "give an informative message" + pass + + def ack(self, msg): + "inform the user of an important event and wait for acknowledgement" + pass + + def debug(self, msg): + "show debugging info" + if self.show_debug: + self.info('DEBUG: ' + msg) + + def warning(self,msg): + "warn the user" + pass + + def error(self,msg): + "inform a (possibly fatal) error" + pass + + def action(self,msg): + "uh?" + pass + + def choose(self, msg, list): + "ask the user to choose from a list of alternatives" + pass + + def confirm(self, msg): + "ask a yes/no question" + pass + + def display_progress(self, pd): + "display progress" + pass diff --git a/pisi/util.py b/pisi/util.py index 33564318..2917f6b6 100644 --- a/pisi/util.py +++ b/pisi/util.py @@ -363,7 +363,7 @@ def clean_locks(top = '.'): for fn in files: if fn.endswith('.lock'): path = os.path.join(root, fn) - ctx.ui.info('Removing lock %s\n', path) + ctx.ui.info('Removing lock %s', path) os.unlink(path) ######################################## diff --git a/pisi/xmlfile.py b/pisi/xmlfile.py index 26adce03..cffc227b 100644 --- a/pisi/xmlfile.py +++ b/pisi/xmlfile.py @@ -24,10 +24,11 @@ in xml.dom :( ) autoxml is a metaclass for automatic XML translation, using - a miniature type system. (w00t!) + a miniature type system. (w00t!) This is based on an excellent + high-level XML processing prototype that Gurer prepared. Method names are mixedCase for compatibility with minidom, - an old library. + an old library. """ # System @@ -38,22 +39,23 @@ import codecs # PiSi import pisi from pisi.xmlext import * +import pisi.context as ctx class Error(pisi.Error): pass - import types mandatory, optional = range(2) # poor man's enum # basic types + Text = types.StringType Integer = types.IntType -class LocalText: - "handle tags with localized text" +class LocalText(object): + """Handles XML tags/attributes with localized text""" def __init__(): locs = {} @@ -80,17 +82,89 @@ class LocalText: if not locs.has_key(L): L = 'en' if not locs.has_key(L): - errs.append("Tag '%s' should have an English version\n" % d[2]) + #errs.append("Tag '%s' should have an English version\n" % d[2]) return "" return locs[L] class autoxml(type): - "high-level automatic XML transformation interface for xmlfile" + """High-level automatic XML transformation interface for xmlfile. + The idea is to declare a class for each XML tag. Inside the + class the tags and attributes nested in the tag are further + elaborated. A simple example follows: + + class Employee: + __metaclass__ = autoxml + t_Name = [xmlfile.Text, xmlfile.mandatory] + a_Type = [xmlfile.Integer, xmlfile.optional] + + This class defines a tag and an attribute nested in Employee + class. Name is a string and type is an integer, called basic + types. + While the tag is mandatory, the attribute may be left out. + + Other basic types supported are: xmlfile.Float, xmlfile.Double + and (not implemented yet): xmlfile.Binary + + By default, the class name is taken as the corresponding tag, + which may be overridden by defining a tag attribute. Thus, + the same tag may also be written as: + + class EmployeeXML: + ... + tag = 'Employee' + ... + + In addition to basic types, we allow for two kinds of complex + types: class types and list types. + + A declared class can be nested in another class as follows + + class Position: + __metaclass__ = autoxml + t_Name = [xmlfile.Text, xmlfile.mandatory] + t_Description = [xmlfile.Text, xmlfile.optional] + + which we can add to our Employee class. + + class Employee: + __metaclass__ = autoxml + t_Name = [xmlfile.Text, xmlfile.mandatory] + a_Type = [xmlfile.Integer, xmlfile.optional] + t_Position = [Position, xmlfile.mandatory] + + Note some unfortunate redundancy here with Position; this is + justified by the implementation (kidding). Still, you might + want to assign a different name than the class name that + goes in there, which may be fully qualified. + + There is more! Suppose we want to define a company, with + of course many employees. + + class Company: + __metaclass__ = autoxml + t_Employees = [ Employee, xmlfile.mandatory] + + Logically, inside the Company tag, we will have several Employee + tags, which are inserted to the Employees instance variable of + Company in order of appearance. + The mandatory flag here asserts that at least one such record + is to be found. + + It is also possible to change the XML path we expect the tag in, + just like with any other tag. + + t_Employees = [ Employee, xmlfile.mandatory, 'Employees/Employee'] + + + You see, it works like magic, when it works of course. All of it + done without a single brain exploding. + + """ def __init__(cls, name, bases, dict): - # add XmlFile as one of the superclasses + # add XmlFile as one of the superclasses, we're smart bases = list(bases) if not XmlFile in bases: bases.append(XmlFile) @@ -98,7 +172,7 @@ class autoxml(type): # standard initialization super(autoxml, cls).__init__(name, bases, dict) - # initialize class attribute __xml_tags + #TODO: initialize class attribute __xml_tags #setattr(cls, 'xml_variables', []) if not dict.has_key('tag'): @@ -119,12 +193,15 @@ class autoxml(type): x = autoxml.gen_tag(cls, name) elif var.startswith('a_'): x = autoxml.gen_attr(cls, name) + ctx.ui.debug(x) (init, decoder, encoder, formatter) = x inits.append(init) decoders.append(decoder) encoders.append(encoder) formatters.append(formatter) + varname = cls.mixed_case(name) + setattr(cls, 'initializers', inits) def initialize(self): for init in self.__class__.initializers: @@ -134,13 +211,13 @@ class autoxml(type): setattr(cls, 'decoders', decoders) def decode(self, node): for decoder in self.__class__.decoders: - decoder(self, node) + setattr(self, varname, decoder(self, node)) setattr(cls, 'decode', decode) setattr(cls, 'encoders', encoders) def encode(self, xml): for encoder in self.__class__.encoders: - encoder(self, xml) + encoder(self, xml, getattr(self, varname)) setattr(cls, 'encode', encode) setattr(cls, 'formatters', formatters) @@ -183,7 +260,9 @@ class autoxml(type): return identifier_p def gen_basic(cls, token, val, readtext, writetext): - "generate a basic tag or attribute" + """Generate a basic tag or attribute. This has got + to be pretty generic so we can invoke it from the complex + types such as Class and List""" name = cls.mixed_case(token) token_type = val[0] req = val[1] @@ -198,25 +277,23 @@ class autoxml(type): try: value = autoxml.basic_cons_map[token_type](text) except Error: - return ['Type mismatch'] + raise Error('Type mismatch') setattr(self, name, value) else: if req == mandatory: - return ['Mandatory argument not available'] + raise Error('Mandatory argument not available') else: - setattr(self, name, None) return None - def encode(self, xml): + def encode(self, xml, value): node = xml.newNode(cls.tag) if hasattr(self, name): - writetext(xml, node, token, str(getattr(self, name))) + writetext(xml, node, token, str(value)) else: if req == mandatory: - return ['Mandatory argument not available'] + raise Error('Mandatory argument not available') def format(self): - #print 'format:', name if hasattr(self, name): return '%s: %s\n' % (token, str(getattr(self, name))) else: @@ -226,69 +303,63 @@ class autoxml(type): return initialize, decode, encode, format - def gen_basic_attr(cls, attr, val): + def gen_basic_attr(cls, attr, spec): """generate an attribute with a basic datatype""" def readtext(node, attr): return getNodeAttribute(node, attr) def writetext(xml, node, attr, value): node.setAttribute(attr, value) - return autoxml.gen_basic(cls, attr, val, readtext, writetext) + return autoxml.gen_basic(cls, attr, spec, readtext, writetext) - def gen_basic_tag(cls, tag, val): + def gen_basic_tag(cls, tag, spec): """generate a tag with a basic datatype""" def readtext(node, tag): return getNodeText(getNode(node, tag)) def writetext(xml, node, tag, value): xml.addTextNodeUnder(node, tag, value) - return autoxml.gen_basic(cls, tag, val, readtext, writetext) + return autoxml.gen_basic(cls, tag, spec, readtext, writetext) def gen_class_tag(cls, tag, val): pass def gen_list_tag(cls, tag, val): - name = cls.mixed_case(token) - token_type = val[0] + name = cls.mixed_case(tag) + tag_type = val[0] req = val[1] if len(val)>=3: - path = val[2] + path = val[2] # an alternative path specified else: - path = tag - if len(token_type!=1): - raise Error('List type must contain one element') - - x = autoxml.gen_tag_aux(cls, tag, tag_type, val) - (x_init, x_decoder, x_encoder, x_formatter) = x + path = tag # otherwise it's the same name as + # the tag + if len(tag_type) != 1: + raise Error('List type must contain only one element') + x = autoxml.gen_tag_aux(cls, tag, tag_type[0], val) + (init_item, decode_item, encode_item, format_item) = x def init(self): - varname = cls.mixed_case(name) - setattr(self, varname, []) + setattr(self, name, []) def decode(self, node): - nodes = getAllNodes(node) - if len(nodes) is 0 and req is mandatory: - pass - #FIXME: add error here - for node in nodes: - x_decoder(self, node) - pass + l = [] + self.nodes = getAllNodes(node, path) + print 'F U', self.nodes + if len(self.nodes) is 0 and req is mandatory: + raise Error('Mandatory list empty') + for node in self.nodes: + l.append(decode_item(self, node)) + return l - def encode(self, xml): - node = xml.newNode(cls.tag) - if hasattr(self, name): - writetext(xml, node, token, str(getattr(self, name))) - else: - if req == mandatory: - return ['Mandatory argument not available'] + def encode(self, xml, value): + pass def format(self): #print 'format:', name - if hasattr(self, name): - return '%s: %s\n' % (token, str(getattr(self, name))) - else: - if req == mandatory: - raise Error('Mandatory variable %s not available' % name) - return "" + s = '' + l = getattr(self, name) + for ix in range(len(l)): + s += str(ix) + format_item(l[ix]) + return s return (init, decode, encode, format)