This commit is contained in:
Eray Özkural
2005-06-18 21:17:29 +00:00
parent 845341782e
commit bb82fd2647
22 changed files with 0 additions and 1272 deletions
-75
View File
@@ -1,75 +0,0 @@
Like every serious project, there are guidelines.
Oooooo. "Coding Standards".
Guidelines
----------
1. When using dirnames, don't expect the dir to end
with a trailing slash, and please use the dirnames
in pisiconfig
2. Python indentation is usually 4 chars.
3. Follow python philosophy of 'batteries included'
4. Don't make the code have runtime dependencies on
a particular distribution (as much as possible)
5. Don't assume narrow use cases.
6. If you are changing something, check if that change
breaks anything and fix breakage. For instance a
name. Running the tests is not always enough!
Unit testing
------------
Unit tests are located in unittests directory. Running the tests is
trivial. But you must synchronize your code and data with the test
code, which can be a tedious work if you lose discipline.
Sample data files are located in samples/ directory.
For running the entire test suite, use the following command:
$ ./unittests/run.py
If you know what you are doing, you can run the tests seperately. But
keep in your mind that tests can depend on each other. (?) The unit test
system doesn't know about that. The following command will run tests
in specfiletests and archivetests in unittests dir:
$ ./unittests/run.py specfile archive
Misc. Suggestions
-----------------
1. Demeter's Law
In OO programming, try to invoke Demeter's law.
One of the "rules" there is not directly accessing any
objects that are further than, 2/3 refs, away. So the
following code is OK.
destroy_system(a.system().name())
but the following isn't as robust
destroy_system(object_store.root().a.system.name())
As you can tell, this introduces too many implementation
dependencies. The rule of thumb is that, in these cases
this statement must have been elsewhere.... It may be a
good idea to not count the object scope in this case,
so in Python self.a means only one level of reference,
not two.
One quibble with this: it may be preferable not to insist
on this where it would be inefficient. So if everything
is neatly packed into one object contained in another
object, why replicate everything in the upper level? If
the semantics prevents dependency changes, then chains
of 3 or even 4 could be acceptable.
OTOH, in Python and C++, it's not always good to implement
accessor/modifier pairs for every property of an object.
It would be much simpler if you are not doing any special
processing on the property (e.g. if what the type system
does is sufficient).
The main rule of thumb in Demeter's Law is avoiding
putting more than, say, 10 methods in a class. That works
really well in practice, forcing refactoring every now
and then.
-1
View File
@@ -1 +0,0 @@
PISI is a new package manager implemented in python.
-23
View File
@@ -1,23 +0,0 @@
PiSi ToDo List
==============
A list of tasks to accomplish, organized into priority sections
Legend:
- Todo
? Not determined if/how we have to do
/ In progress
+ Accomplished
1. Pre-Alpha
+ implement reading spec file
/ implement install database
? transaction/locking for database
2. Alpha
3. Beta
-58
View File
@@ -1,58 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# python standard library
from os.path import basename
import pisi.context
import pisi.util
import pisi.ui
from pisi.build import PisiBuild, PisiBuildError
def usage(progname = "pisi-build"):
print """
Usage:
%s [options] package-name.pspec
""" %(progname)
def main():
import sys
import getopt
# wrapper for usage(progname) function
help = lambda: usage(sys.argv[0])
# getopt magic
try:
opts, args = getopt.getopt(sys.argv[1:],"h",["help"])
except getopt.GetoptError:
help()
sys.exit(1)
for opt, arg in opts:
if opt == "-h":
help()
sys.exit(1)
# pspec(PISI Spec) to be used for package
pspec = ""
# TODO: We accept only one pspec file (at a time) currently,
# but pisi-build may build many packages (pspecs given in order)
# sequentially.
if len(args) != 1:
help()
raise PisiBuildError, "pisi-build expects one (and only one) pspec file currently"
else:
pspec = args[0]
# What we need to do first is create a context with our specfile
ctx = pisi.context.Context(pspec)
# don't do the real job here. this is just a CLI!
pb = PisiBuild(ctx)
pb.build()
if __name__ == "__main__":
main()
-43
View File
@@ -1,43 +0,0 @@
#! /usr/bin/python
# -*- coding: utf-8 -*-
# sys modules
import sys
from optparse import OptionParser
import pisi.install
import pisi.util
class ArgError(Exception):
pass
def main():
usage = "usage: %prog [options] <package>"
parser = OptionParser(usage=usage,version="%prog " + pisi.__version__)
parser.add_option("-D", "--destdir", action="store")
parser.add_option("-v", "--verbose", action="store_true",
dest="verbose", default=False,
help="detailed output")
parser.add_option("-d", "--debug", action="store_true", default=True)
parser.add_option("-n", "--dry-run", action="store_true",
default = "do not perform any action, just show what\
would be done")
(options, args) = parser.parse_args()
# package filename
package_fn = ""
# TODO: We accept only one package file arg ATM
if len(args) != 1:
print usage
raise ArgError, "pisi-install expects one (and only one) package file currently"
else:
package_fn = args[0]
pisi.install.install_package_file(package_fn)
if __name__ == "__main__":
main()
-4
View File
@@ -1,4 +0,0 @@
# PISI package version
__version__ = "0.1"
-103
View File
@@ -1,103 +0,0 @@
# -*- coding: utf-8 -*-
# unpack magic
# maintainer baris and meren
#standart lisbrary modules
import os
import sys
import tarfile
import zipfile
#pisi modules
import util
class ArchiveBase(object):
def __init__(self, ctx):
self.type = ctx.spec.source.archiveType
self.fileName = os.path.basename(ctx.spec.source.archiveUri)
self.filePath = ctx.archives_dir() + '/' + self.fileName
def unpack(self, targetDir):
self.targetDir = targetDir
# first we check if we need to clean-up our working env.
if os.path.exists(self.targetDir):
util.clean_dir(self.targetDir)
else:
os.makedirs(self.targetDir)
class ArchiveTarFile(ArchiveBase):
def __init__(self, ctx):
super(ArchiveTarFile, self).__init__(ctx)
def unpack(self, targetDir):
super(ArchiveTarFile, self).unpack(targetDir)
rmode = ""
if self.type == 'tar':
rmode = 'r:'
elif self.type == 'targz':
rmode = 'r:gz'
elif self.type == 'tarbz2':
rmode = 'r:bz2'
tar = tarfile.open(self.filePath, rmode)
oldwd = os.getcwd()
os.chdir(self.targetDir)
for tarinfo in tar:
tar.extract(tarinfo)
os.chdir(oldwd)
tar.close()
class ArchiveZip(ArchiveBase):
def __init__(self, ctx):
super(ArchiveZip, self).__init__(ctx)
def unpack(self, targetDir):
super(ArchiveZip, self).unpack(targetDir)
zip = zipfile.ZipFile(self.filePath, 'r')
for file in zip.namelist():
ofile = self.targetDir + '/' + file
# a directory is present. lets continue
if os.path.isdir(ofile):
continue
# do we need to create parent directory for our file?
if not os.path.exists(os.path.dirname(ofile)):
os.mkdir(ofile)
continue
info = zip.getinfo(file)
# O.K. we know following line is dull. What we wanted to
# do was to compare the equality to 0xa0000000. But there
# is a known problem in Python regarding the hex/oct
# constants. Please see Guido's explanation at
# http://mail.python.org/pipermail/python-dev/2003-February/033029.html
if hex(info.external_attr)[2] == 'A':
target = zip.read(file)
os.symlink(target, ofile)
else:
buff = open (ofile, 'wb')
fileContent = zip.read(file)
buff.write(fileContent)
buff.close()
zip.close()
class Archive:
"""Unpack magic for Archive files..."""
def __init__(self, ctx):
"""accepted archive types:
targz, tarbz2, zip, tar"""
handlers = {
'targz': ArchiveTarFile,
'tarbz2': ArchiveTarFile,
'tar': ArchiveTarFile,
'zip': ArchiveZip
}
type = ctx.spec.source.archiveType
self.archive = handlers.get(type)(ctx)
def unpack(self, targetDir):
self.archive.unpack(targetDir)
-132
View File
@@ -1,132 +0,0 @@
# -*- coding: utf-8 -*-
# package bulding stuff
# maintainer: baris and meren
# python standard library
import os
from fetcher import Fetcher
from archive import Archive
# import pisipackage
import util
from ui import ui
class PisiBuildError(Exception):
pass
# FIXME: this eventually has to go to ui module
# Infact all ui calls has nothing to do with this build process.
# There more of them in PisiBuild...
# And maybe we should consider moving PisiBuild.build() back to pisi-build
# CLI too.
# exa: This, like all others, will have a GUI or CLI, interchangeably
def displayProgress(pd):
out = '\r%-30.30s %3d%% %12.2f %s' % \
(pd['filename'], pd['percent'], pd['rate'], pd['symbol'])
ui.info(out)
class PisiBuild:
"""PisiBuild class, provides the package build and creation routines"""
def __init__(self, context):
self.ctx = context
self.work_dir = self.ctx.pkg_work_dir()
self.spec = self.ctx.spec
def build(self):
ui.info("Building PISI source package: %s\n" % self.spec.source.name)
ui.info("Fetching source from: %s\n" % self.spec.source.archiveUri)
self.fetchArchive(displayProgress)
ui.info("Source archive is stored: %s/%s\n"
%(self.ctx.archives_dir(), self.spec.source.archiveName))
self.solveBuildDependencies()
ui.info("Unpacking archive...")
self.unpackArchive()
ui.info(" unpacked (%s)\n" % self.ctx.pkg_work_dir())
self.applyPatches()
try:
specdir = os.path.dirname(self.ctx.pspecfile)
self.actionScript = open("/".join([specdir,self.ctx.const.actions_file])).read()
except IOError, e:
ui.error ("Action Script: %s\n" % e)
return
# FIXME: It's wrong to assume that unpacked archive
# will create a name-version top-level directory.
# Archive module should give the exact location.
# (from the assumption is evil dept.)
os.chdir(self.ctx.pkg_work_dir() + "/" + self.spec.source.name + "-" + self.spec.source.version)
locals = globals = {}
try:
exec compile(self.actionScript , "error", "exec") in locals,globals
except SyntaxError, e:
ui.error ("Error : %s\n" % e)
return
self.configureSource(locals)
self.buildSource(locals)
self.installSource(locals)
# after all, we are ready to build/prepare the packages
self.buildPackages()
def fetchArchive(self, percentHook=None):
"""fetch an archive and store to ctx.archives_dir()
using fether.Fetcher"""
fetch = Fetcher(self.ctx)
# check if source already cached
destpath = fetch.filedest + "/" + fetch.filename
if os.access(destpath, os.R_OK):
if util.md5_file(destpath) == self.spec.source.archiveMD5:
ui.info('%s [cached]\n' % self.spec.source.archiveName)
return
if percentHook:
fetch.percentHook = percentHook
fetch.fetch()
# FIXME: What a ugly hack! We should really find a cleaner way for output.
if percentHook:
ui.info('\n')
def solveBuildDependencies(self):
pass
def unpackArchive(self):
archive = Archive(self.ctx)
archive.unpack(self.work_dir)
def applyPatches(self):
pass
def configureSource(self, locals):
func = self.ctx.const.setup_func
if func in locals:
ui.info("Configuring %s...\n" % self.spec.source.name)
locals[func]()
def buildSource(self, locals):
func = self.ctx.const.build_func
if func in locals:
ui.info("Building %s...\n" % self.spec.source.name)
locals[func]()
def installSource(self, locals):
func = self.ctx.const.install_func
if func in locals:
ui.info("Installing %s...\n" % self.spec.source.name)
locals[func]()
def buildPackages(self):
for package in self.spec.packages:
ui.info("** Building package %s\n" % package.name);
-48
View File
@@ -1,48 +0,0 @@
colors = {'black' : "\033[30m",
'red' : "\033[31m",
'green' : "\033[32m",
'yellow' : "\033[33m",
'blue' : "\033[34m",
'purple' : "\033[35m",
'cyan' : "\033[36m",
'white' : "\033[37m",
'brightblack' : "\033[01;30m",
'brightred' : "\033[01;31m",
'brightgreen' : "\033[01;32m",
'brightyellow' : "\033[01;33m",
'brightblue' : "\033[01;34m",
'brightmagenta' : "\033[01;35m",
'brightcyan' : "\033[01;36m",
'brightwhite' : "\033[01;37m",
'underlineblack' : "\033[04;30m",
'underlinered' : "\033[04;31m",
'underlinegreen' : "\033[04;32m",
'underlineyellow' : "\033[04;33m",
'underlineblue' : "\033[04;34m",
'underlinemagenta' : "\033[04;35m",
'underlinecyan' : "\033[04;36m",
'underlinewhite' : "\033[04;37m",
'blinkingblack' : "\033[05;30m",
'blinkingred' : "\033[05;31m",
'blinkinggreen' : "\033[05;32m",
'blinkingyellow' : "\033[05;33m",
'blinkingblue' : "\033[05;34m",
'blinkingmagenta' : "\033[05;35m",
'blinkingcyan' : "\033[05;36m",
'blinkingwhite' : "\033[05;37m",
'backgroundblack' : "\033[07;30m",
'backgroundred' : "\033[07;31m",
'backgroundgreen' : "\033[07;32m",
'backgroundyellow' : "\033[07;33m",
'backgroundblue' : "\033[07;34m",
'backgroundmagenta' : "\033[07;35m",
'backgroundcyan' : "\033[07;36m",
'backgroundwhite' : "\033[07;37m",
'default' : "\033[0m" }
def colorize(msg, color):
if colors.has_key(color):
return colors[color] + msg + colors['default']
else:
return msg
-104
View File
@@ -1,104 +0,0 @@
# -*- coding: utf-8 -*-
# PISI configuration (static and dynamic)
from specfile import SpecFile
import oo
class Constants:
"Pisi constants"
c = oo.const()
def __init__(self):
self.c.lib_dir_suffix = "/var/lib/pisi"
self.c.db_dir_suffix = "/var/db/pisi"
self.c.archives_dir_suffix = "/var/cache/pisi/archives"
self.c.tmp_dir_suffix = "/var/tmp/pisi"
# directory suffixes for build
self.c.work_dir_suffix = "/work"
self.c.install_dir_suffix = "/install"
# file/directory names
self.c.actions_file = "actions.py"
self.c.files_dir = "files"
# functions in actions_file
self.c.setup_func = "setup"
self.c.build_func = "build"
self.c.install_func = "install"
def __getattr__(self, attr):
return getattr(self.c, attr)
def __setattr__(self, attr, value):
return setattr(self.c, attr, value)
def __delattr__(self, attr):
return delattr(self.c, attr)
class Context(object):
"""Config/Context Singleton"""
class __impl:
def __init__(self):
self.const = Constants()
# self.c.destdir = '' # install default to root by default
self.destdir = './tmp' # only for ALPHA
# the idea is that destdir can be set with --destdir=...
def setSpecFile(self, pspecfile):
self.pspecfile = pspecfile
spec = SpecFile()
spec.read(pspecfile)
spec.verify() # check pspec integrity
self.spec = spec
# directory accessor functions
# here is how it goes
# x_dir: system wide directory for storing info type x
# pkg_x_dir: per package directory for storing info type x
def lib_dir(self):
return self.destdir + self.const.lib_dir_suffix
def db_dir(self):
return self.destdir + self.const.db_dir_suffix
def archives_dir(self):
return self.destdir + self.const.archives_dir_suffix
def tmp_dir(self):
return self.destdir + self.const.tmp_dir_suffix
def pkg_work_dir(self):
packageDir = self.spec.source.name + '-' \
+ self.spec.source.version + '-' + self.spec.source.release
return self.destdir + self.const.tmp_dir_suffix \
+ '/' + packageDir + self.const.work_dir_suffix
def install_dir(self):
return self.tmp_dir() + self.const.install_dir_suffix
def pkg_install_dir(self):
packageDir = self.spec.source.name + '-' \
+ self.spec.source.version + '-' + self.spec.source.release
return self.destdir + self.const.tmp_dir_suffix \
+ '/' + packageDir + self.const.install_dir_suffix
__instance = __impl()
def __init__(self, pspecfile = None):
if pspecfile != None:
self.__instance.setSpecFile(pspecfile)
def __getattr__(self, attr):
return getattr(self.__instance, attr)
def __setattr__(self, attr, value):
return setattr(self.__instance, attr, value)
# create a default context WITH NO PSPEC
ctx = Context()
-3
View File
@@ -1,3 +0,0 @@
# dependency analyzer
# maintainer: eray and caglar
-139
View File
@@ -1,139 +0,0 @@
# -*- coding: utf-8 -*-
# download magic
# maintainer: baris and meren
# python standard library modules
import urlparse
import urllib2
import os
# pisi modules
import util
class FetchError (Exception):
pass
class Fetcher:
"""Yet another Pisi tool for fetching files from various sources.."""
def __init__(self, ctx):
self.uri = ctx.spec.source.archiveUri
self.filedest = ctx.archives_dir()
util.check_dir(self.filedest)
self.scheme = "file"
self.netloc = ""
self.filepath = ""
self.filename = ""
self.percent = 0
self.rate = 0.0
self.percentHook = None
from string import split
u = urlparse.urlparse(self.uri)
self.scheme, self.netloc, self.filepath = u[0], u[1], u[2]
self.filename = os.path.basename(self.uri)
def fetch (self):
"""Return value: Fetched file's full path.."""
if self.filename == "":
self.err("filename error")
if os.access(self.filedest, os.W_OK) == False:
self.err("no perm to write to dest dir")
scheme_err = lambda: self.err("unexpected scheme")
handlers = {
'file': self.fetchLocalFile,
'http': self.fetchRemoteFile,
'ftp' : self.fetchRemoteFile
}; handlers.get(self.scheme, scheme_err)()
return self.filedest + "/" + self.filename
def doGrab(self, file, dest, totalsize):
symbols = [' B/s', 'KB/s', 'MB/s', 'GB/s']
from time import time
tt, oldsize = int(time()), 0
p = Progress(totalsize)
bs, size = 1024, 0
symbol, depth = "B/s", 0
st = time()
chunk = file.read(bs)
size = size + len(chunk)
self.percent = p.update(size)
while chunk:
dest.write(chunk)
chunk = file.read(bs)
size = size + len(chunk)
ct = time()
if int(tt) != int(ct):
self.rate = size / (ct - st)
while self.rate > 1000 and depth < 3:
self.rate /= 1024
depth += 1
symbol, depth = symbols[depth], 0
oldsize, tt = size, time()
if p.update(size):
self.percent = p.percent
if self.percentHook != None:
retval = {'filename': self.filename,
'percent' : self.percent,
'rate': self.rate,
'symbol': symbol}
self.percentHook(retval)
dest.close()
def fetchLocalFile (self):
from shutil import copyfile
if os.access(self.filepath, os.F_OK) == False:
self.err("no such file or no perm to read")
dest = open(self.filedest + "/" + self.filename , "w")
totalsize = os.path.getsize(self.filepath)
file = open(self.filepath)
self.doGrab(file, dest, totalsize)
def fetchRemoteFile (self):
from httplib import HTTPException
try:
file = urllib2.urlopen(self.uri)
headers = file.info()
except ValueError, e:
self.err('%s' % (e, ))
except IOError, e:
self.err('%s' % (e, ))
except OSError, e:
self.err('%s' % (e, ))
except HTTPException, e:
self.err(('(%s): %s') % (e.__class__.__name__, e))
if not headers is None and not headers.has_key('Content-Length'):
self.err('file not found')
else: totalsize = int(headers['Content-Length'])
dest = open(self.filedest + "/" + self.filename , "w")
self.doGrab(file, dest, totalsize)
def err (self, error):
raise FetchError(error)
class Progress:
def __init__(self, totalsize):
self.totalsize = totalsize
self.percent = 0
def update(self, size):
percent = (size * 100) / self.totalsize
if percent and self.percent is not percent:
self.percent = percent
return percent
else:
return 0
-46
View File
@@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
#import package
from specfile import *
from package import Package
import util
from context import ctx
from ui import ui
import installdb
import packagedb
import dependency
#import conflicts
class InstallError(Exception):
pass
def install_package_file(package_fn):
package = Package(package_fn, 'r')
# extract control files
util.clean_dir(ctx.install_dir())
ui.info('extracting files\n')
package.extract_PISI_files(ctx.install_dir())
# verify package
# check if we have all required files
metadata = MetaData()
metadata.read(ctx.install_dir() + '/metadata.xml')
# check package semantics
if not metadata.verify():
raise InstallError("MetaData format wrong")
# check file system requirements
# what to do if / is split into /usr, /var, etc.?
# check conflicts
# check dependencies
# unzip package in place
# update databases
# installdb
installdb.install(spec.spec.install_dir() + '/files.xml')
-52
View File
@@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
# installation database
# maintainer: eray and caglar
import os
import bsddb.dbshelve as shelve
from context import ctx
import util
util.check_dir(ctx.db_dir())
d = shelve.open(ctx.db_dir() + '/install.bdb')
files_dir = ctx.archives_dir() + "/files"
class InstallDBError(Exception):
pass
def files_name(name, version, release):
return files_dir + '/' + name + '-' + version + '-' + release
def files(n, v, r):
return file(files_name(n,v,r))
def is_recorded(name, version, release):
key = name + version + release
return d.has_key(key)
def is_installed(name, version, release):
key = name + version + release
return is_recorded(name,version,release) and d[key]=='i'
def is_removed(name, version, release):
key = name + version + release
return is_recorded(name,version,release) and d[key]=='r'
def install(name, version, release, files_xml):
key = name + version + release
if is_installed(name, version, release):
raise InstallDBError("already installed")
d[key] = 'i'
util.copy_file(files_xml, files_name(name, version, release))
def remove(name, version, release):
key = name + version + release
d[key] = 'r'
def purge(name, version, release):
os.unlink(files_name(name, version, release))
key = name + version + release
del d[key]
-20
View File
@@ -1,20 +0,0 @@
# OO extensions
# thes are really cool, you can't do this in C++ :)
class const:
"Constant members implementation"
class ConstError(TypeError):
pass
def __setattr__(self, name, value):
if self.__dict__.has_key(name):
raise self.ConstError, "Can't rebind constant: %s" % name
# Binding an attribute once to a const is available
self.__dict__[name] = value
def __delattr__(self, name):
if self.__dict__.has_key(name):
raise self.ConstError, "Can't unbind constant: %s" % name
# we don't have an attribute by this name
raise NameError, name
-30
View File
@@ -1,30 +0,0 @@
# package abstraction
# provides methods to add/remove files, extract control files
# maintainer: baris and meren
class Package:
"""Package: PISI package class"""
def __init__(self, packagefn, mode):
self.filename = packagefn
self.mode = mode # bu gerekli mi?
# etc. etc.
def add_file(self, fn):
"""add a file to package"""
def extract(self, outdir):
"""extract entire package contents to directory"""
extract_dir('', outdir) # means package root
def extract_file(self, path, outdir):
"""extract file with path to outdir"""
def extract_dir(self, dir, outdir):
"""extract directory recursively"""
def extract_PISI_files(self, outdir):
"""extract PISI control files: metadata.xml, files.xml,
action scripts, etc."""
self.extract_file('metadata.xml', outdir)
self.extract_file('files.xml', outdir)
self.extract_dir('Config', outdir)
-28
View File
@@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# package database
# interface for update/query to local package repository
# maintainer: eray and caglar
# we basically store everything in PackageInfo class
# yes, we are cheap
import bsddb.dbshelve as shelve
import util
from context import ctx
util.check_dir(ctx.db_dir())
d = shelve.open(ctx.db_dir() + '/package.bdb')
def has_package(name):
return d.has_key(name)
def get_package(name):
return d[name]
def add_package(name, package_info):
d[name] = package_info
def remove_package(name):
del d[name]
-18
View File
@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
# package source database
# interface for update/query to local package repository
# maintainer: eray and caglar
# we basically store everything in sourceinfo class
# yes, we are cheap
import bsddb.dbshelve as shelve
util.check_dir(config.db_dir())
d = shelve.open(config.db_dir() + '/source.bdb')
def add_source(name, source_info):
d[name] = source_info
def remove_source(name):
del d[name]
-105
View File
@@ -1,105 +0,0 @@
# -*- coding: utf-8 -*-
# read/write PISI source package specification file
import xml.dom.minidom
from xmlfile import *
from os.path import basename
class PatchInfo:
def __init__(self, filenm, ctype):
self.filename = filenm
self.compressionType = ctype
def __init__(self, node):
self.filename = getNodeText(node)
self.compressionType = getNodeAttribute(node, "compressionType")
class DepInfo:
def __init__(self, node):
self.package = getNodeText(node).strip()
self.versionFrom = getNodeAttribute(node, "versionFrom")
class HistoryInfo:
def __init__(self, node):
self.date = getNodeText(getNode(node, "Date"))
self.version = getNodeText(getNode(node, "Version"))
self.release = getNodeText(getNode(node, "Release"))
class PathInfo:
def __init__(self, node):
self.pathname = getNodeText(node)
self.fileType = getNodeAttribute(node, "fileType")
# a structure to hold source information
class SourceInfo:
pass
class PackageInfo:
def __init__(self, node):
self.name = getNodeText(getNode(node, "Name"))
self.summary = getNodeText(getNode(node, "Summary"))
self.description = getNodeText(getNode(node, "Description"))
self.category = getNodeText(getNode(node, "Category"))
iDepElts = getAllNodes(node, "InstallDependencies")
self.installDeps = [DepInfo(x) for x in iDepElts]
rtDepElts = getAllNodes(node, "RuntimeDependencies")
self.runtimeDeps = [DepInfo(x) for x in rtDepElts]
self.paths = [PathInfo(x) for x in getAllNodes(node, "Files/Path")]
class SpecFile(XmlFile):
"""A class for reading/writing from/to a PSPEC (PISI SPEC) file."""
def __init__(self):
XmlFile.__init__(self,"PSPEC")
def read(self, filename):
"""Read PSPEC file"""
self.readxml(filename)
self.source = SourceInfo()
self.source.name = self.getChildText("Source/Name")
archiveNode = self.getNode("Source/Archive")
self.source.archiveUri = getNodeText(archiveNode).strip()
self.source.archiveName = basename(self.source.archiveUri)
self.source.archiveType = getNodeAttribute(archiveNode, "archType")
self.source.archiveMD5 = getNodeAttribute(archiveNode, "md5sum")
patchElts = self.getChildElts("Source/Patches")
if patchElts:
self.source.patches = [PatchInfo(p) for p in patchElts]
buildDepElts = self.getChildElts("Source/BuildDependencies")
if buildDepElts:
self.source.buildDeps = [DepInfo(d) for d in buildDepElts]
historyElts = self.getAllNodes("History/Update")
self.source.history = [HistoryInfo(x) for x in historyElts]
# As we have no Source/Version tag we need to get
# the last version and release information
# from the first child of History/Update. And it works :)
self.source.version = self.source.history[0].version
self.source.release = self.source.history[0].release
# find all binary packages
packageElts = self.getAllNodes("Package")
self.packages = [PackageInfo(p) for p in packageElts]
def verify(self):
"""Verify PSPEC structures, are they what we want of them?"""
return True
def write(self, filename):
"""Write PSPEC file"""
self.writexml(filename)
class MetaData(SpecFile):
"""This is a superset of the source spec definition"""
def read(self, filename):
SpecFile.read(filename)
distribution = self.getNodeText("Source/Distribution")
distributionRelease = self.getNodeText("Source/DistributionRelease")
architecture = self.getNodeText("Source/Architecture")
installSize = self.getNodeText("Source/InstallSize")
-30
View File
@@ -1,30 +0,0 @@
# generic user interface
import sys
from colors import colorize
def register(_impl):
""" Register a UI implementation"""
ui = _impl
# default UI implementation
class CLI:
def __init__(self, debuggy = True):
self.showDebug = debuggy
def info(self, msg):
sys.stdout.write(colorize(msg, 'blue'))
sys.stdout.flush()
def debug(self, msg):
if showDebug:
sys.stdout.write(msg)
sys.stdout.flush()
def error(self,msg):
sys.stdout.write(colorize(msg, 'red'))
sys.stdout.flush()
# default UI is CLI
ui = CLI()
-69
View File
@@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
# misc. utility functions, including process and file utils
# maintainer: eray and caglar and baris and meren!
import os
import sys
import md5
from ui import ui
class FileError(Exception):
pass
class UtilError(Exception):
pass
# shorthand to check if a file exists
def check_file(file, mode = os.F_OK):
if not os.access(file, mode):
raise FileError("File " + file + " not found")
# check if directory exists, and create if it doesn't
# works recursively
# FIXME: could have a better name
def check_dir(dir):
dir = dir.strip().rstrip("/")
if not os.access(dir, os.F_OK):
os.makedirs(dir)
def clean_dir(top):
"""Remove all content of a directory (top)"""
for root, dirs, files in os.walk(top, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
def copy_file(s,d):
check_file(s)
check_dir(os.path.dirname(d))
fs = file(s, 'rb')
fd = file(d, 'wb')
for l in fs:
fd.write(l)
def copy_dir():
raise UtilError("not implemented")
def md5_file(filename):
m = md5.new()
f = file(filename, 'rb')
for l in f:
m.update(l)
return m.hexdigest()
# run a command non-interactively
def run_batch(cmd):
ui.info('running ' + cmd)
a = os.popen(cmd)
lines = a.readlines()
ret = a.close()
ui.debug('return value ' + ret)
successful = ret == None
if not successful:
ui.error('ERROR: executing command: ' + cmd + '\n' + strlist(lines))
return (successful,lines)
# print a list
def strlist(l):
return string.join(map(lambda x: str(x) + ' ', l))
-141
View File
@@ -1,141 +0,0 @@
# -*- coding: utf-8 -*-
# some helper functions for using minidom
import xml.dom.minidom as mdom
class XmlError(Exception):
pass
# static functions
def getNodeAttribute(node, attrname):
for i in range(node.attributes.length):
attr = node.attributes.item(i)
if attr.name == attrname:
return attr.childNodes[0].data
def getNodeText(node):
# get the first child
try:
child = node.childNodes[0]
except IndexError:
return None
except AttributeError: # no node by that name
return None
if child.nodeType == child.TEXT_NODE:
return child.data
else:
raise XmlError("getNodeText: Expected text node, got something else!")
def getChildText(node_s, tagpath):
node = getNode(node_s, tagpath)
if not node:
return None
return getNodeText(node)
def getChildElts(node):
"""get only child elements"""
return filter(lambda x:x.nodeType == x.ELEMENT_NODE, node.childNodes)
def getNode(node, tagpath):
"""returns the *first* matching node for given tag path."""
tags = tagpath.split('/')
# iterative code to search for the path
# get DOM for top node
nodeList = node.getElementsByTagName(tags[0])
if len(nodeList) == 0:
return None # not found
node = nodeList[0] # discard other matches
for tag in tags[1:]:
nodeList = node.getElementsByTagName(tag)
if len(nodeList) == 0:
return None
else:
node = nodeList[0]
return node
def getAllNodes(node, tags):
"""retrieve all nodes that match a given tag path."""
if len(tags) == 0:
return []
nodeList = node.getElementsByTagName(tags[0])
if len(nodeList) == 0:
return []
for tag in tags[1:]:
results = map(lambda x: x.getElementsByTagName(tag),nodeList)
nodeList = []
for x in results:
nodeList.extend(x)
pass # emacs indentation error, keep it here
if len(nodeList) == 0:
return []
return nodeList
# xmlfile class that further abstracts a dom object
class XmlFile(object):
"""A class for retrieving information from an XML file"""
def __init__(self, rootTag):
self.rootTag = rootTag
def readxml(self, fileName):
self.dom = mdom.parse(fileName)
def writexml(self, fileName):
f = file(fileName,'w')
self.dom.writexml(f)
def verifyRootTag(self):
if self.dom.documentElement.tagName != self.rootTag:
raise XmlError("Root tagname not " + self.rootTag + " as expected")
def getNode(self, tagPath):
"""returns the *first* matching node for given tag path."""
self.verifyRootTag()
return getNode(self.dom.documentElement, tagPath)
def getAllNodes(self, tagPath):
"""returns all nodes matching a given tag path."""
self.verifyRootTag()
tags = tagPath.split('/')
return getAllNodes(self.dom.documentElement, tags)
def getChildren(self, tagpath):
""" returns the children of the given path"""
node = self.getNode(tagpath)
return node.childNodes
# get only elements of a given type
# BUG: this doesn't work
def getChildrenWithType(self, tagpath, type):
""" returns the children of the given path, only with given type """
node = self.getNode(tagpath)
return filter(lambda x:x.nodeType == type, node.childNodes)
# get only child elements
def getChildElts(self, tagpath):
""" returns the children of the given path, only with given type """
node = self.getNode(tagpath)
try:
return filter(lambda x:x.nodeType == x.ELEMENT_NODE, node.childNodes)
except AttributeError:
return None
def getChildText(self, tagpath):
node = self.getNode(tagpath)
if not node:
return None
return getNodeText(node)