#! /usr/bin/env python # arqive - Yet another pacman front / end. # Copyright (C) 2007 Ingmar K. Steen (iksteen@gmail.com) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os, shelve, re, stat class PacmanConfException(Exception): pass class PacmanConfParser: def __init__(self, path = '/etc/pacman.conf'): self.path = path self.options = \ { 'LogFile': '', 'NoUpgrade': [], 'HoldPkg': [], 'IgnorePkg': [], 'XferCommand': '' } self.repositories = {} self.section_re = re.compile('\[(.*)\]') self.value_re = re.compile('(.*)=(.*)') def parse(self): f = open(self.path, 'r') section = None lineno = 0 while 1: lineno += 1 lineraw = f.readline() if not lineraw: break lineraw = lineraw.strip() line = lineraw.split('#')[0].strip() if not line: continue match = self.section_re.match(line) if match: section = match.groups()[0] if section != "options": if not self.repositories.has_key(section): self.repositories[section] = [] continue if section is None: raise PacmanConfException('Data without a section') match = self.value_re.match(line) if not match: raise PacmanConfException("Parsing error in line %i ('%s')" % (lineno, lineraw)) key, val = [v.strip() for v in match.groups()] if section == "options": if not self.options.has_key(key): raise PacmanConfException("Unknown option ('%s') in line %i ('%s')" % (key, lineno, lineraw)) if type(self.options[key]) == list: self.options[key].append([v.strip() for v in val.split(' ') if v.strip()]) else: self.options[key] = val elif key == "Server": self.repositories[section].append((val, True)) elif key == "Include": self.parse_include(section, val) else: raise PacmanConfException("Unknown repository option ('%s') in line %i ('%s')" % (key, lineno, lineraw)) def parse_include(self, include_section, include_path): f = open(include_path, 'r') section = None lineno = 0 while 1: lineno += 1 lineraw = f.readline() if not lineraw: break lineraw = lineraw.strip() line = lineraw.split('#')[0].strip() if not line: continue match = self.section_re.match(line) if match: section = match.groups()[0] continue if section is None: raise PacmanConfException('Data without a section') match = self.value_re.match(line) if not match: raise PacmanConfException("Parsing error in line %i ('%s')" % (lineno, lineraw)) if section != include_section: continue key, val = [v.strip() for v in match.groups()] if key == "Server": self.repositories[section].append((val, False)) elif key == "Include": self.parse_include(section, val) else: raise PacmanConfException("Unknown repository option ('%s') in line %i ('%s')" % (key, lineno, lineraw)) class PackageDatabase: def __init__(self, repository, path = None): self.section_re = re.compile('\\%(.*)\\%') self.repository = repository self.path = path if self.path is None: self.path = '/var/lib/pacman/%s' % repository if not os.path.isdir(os.path.expanduser('~/.arqive')): os.makedirs(os.path.expanduser('~/.arqive')) self.dbpath = os.path.expanduser('~/.arqive/%s.pkg.db' % repository) self.db = shelve.open(self.dbpath, writeback = True) def update(self): mtime = os.stat(self.path)[stat.ST_MTIME] if self.db.get('__mtime__', 0) == mtime: return cache = {} for dir in os.listdir(self.path): path = os.path.join(self.path, dir) if not os.path.isdir(path): continue package, version, revision = dir.rsplit('-', 2) if self.db.has_key(package): pkg = self.db[package] db_ver = pkg['version'] db_rev = pkg['revision'] if db_ver == version and db_rev == revision: continue print "[%s] Updating info for '%s'." % (self.repository, package) pkg_info = self.parse_package(path, package) if pkg_info is None: continue pkg_info.update({ 'version': version, 'revision': revision }) self.db[package] = pkg_info cache_key = package.lower() + ' ' + pkg_info['DESC'].lower() cache[cache_key] = package dump = [] for key, value in self.db.items(): if key in ('__mtime__', '__cache__'): continue if not os.path.isdir('%s/%s-%s-%s' % (self.path, key, str(value['version']), str(value['revision']))): print "[%s] Dumping package '%s'." % (self.repository, key) dump.append(key) for key in dump: del self.db[key] self.db['__mtime__'] = mtime self.db['__cache__'] = cache def parse_package(self, path, package): pkg_info = { 'CONFLICTS': [], 'CSIZE': 0, 'DEPENDS': [], 'DESC': '', 'FORCE': False, 'GROUPS': [], 'MD5SUM': '', 'NAME': '', 'PROVIDES': [], 'REPLACES': [], 'VERSION': '', 'URL': '', 'BUILDDATE': '', 'ARCH': '', 'INSTALLDATE': '', 'PACKAGER': '', 'REASON': 0, 'LICENSE': [], 'SIZE': 0, # 'FILES': [], # 'BACKUP': [], } if not os.path.exists(path + '/desc'): print "[%s] %s: Invalid package (no package description)" return None info = self.parse_package_file(path + '/desc') if not info: return None for key, value in info.items(): if not pkg_info.has_key(key): print 'Skipping %s element.' % key t = type(pkg_info[key]) if t is bool: pkg_info[key] = True elif t is list: pkg_info[key] = value elif t is str: pkg_info[key] = '\n'.join(value) elif t is int: pkg_info[key] = int(value[-1]) else: raise Exception('Ahh freck it...') if os.path.exists(path + '/depends'): info = self.parse_package_file(path + '/depends') if not info: return None if info.has_key('DEPENDS'): pkg_info['DEPENDS'] += info['DEPENDS'] if info.has_key('CONFLICTS'): pkg_info['CONFLICTS'] += info['CONFLICTS'] if info.has_key('PROVIDES'): pkg_info['PROVIDES'] += info['PROVIDES'] # FIXME: This should go into a seperate database # if os.path.exists(path + '/files'): # info = self.parse_package_file(path + '/files') # if not info: # return None # if info.has_key('FILES'): # pkg_info['FILES'] += info['FILES'] # if info.has_key('BACKUP'): # pkg_info['BACKUP'] += info['BACKUP'] return pkg_info def parse_package_file(self, path): info = {} section = None f = open(path) while 1: line = f.readline() if not line: break line = line.strip() if not line: continue match = self.section_re.match(line) if match: section = match.group(1) if not info.has_key(section): info[section] = [] continue if section is None: print "Data without section in '%s'." % path return None info[section].append(line) return info def has_package(self, package): return self.db.has_key(package) def find(self, queries): if type(queries) is str: queries = [queries] queries = [q.lower() for q in queries] result = [] for key, value in self.db['__cache__'].items(): for query in queries: if query in key: result.append(('%s/%s' % (self.repository, value), self.db[value])) return result def print_results(results): for result in results: print '%s %s-%s' % (result[0], result[1]['version'], result[1]['revision']) for line in result[1]['DESC'].split('\n'): l = 3 sys.stdout.write(' ') for word in line.split(): if (l + len(word) + 1) >= 80 and l > 3: sys.stdout.write('\n ') l = 3 sys.stdout.write(' ' + word) l += len(word) + 1 print if __name__ == '__main__': import sys if (len(sys.argv) < 3) or (sys.argv[1] not in ('-s', '-i')): print 'Usage: %s -s [query..]: search package descriptions' % sys.argv[0] print ' %s -i [query..]: check if a package is installed' % sys.argv[0] sys.exit(-1) if sys.argv[1] == '-s': config = PacmanConfParser() config.parse() databases = {} for repository in config.repositories.keys(): databases[repository] = db = PackageDatabase(repository) db.update() queries = [q.lower() for q in sys.argv[2:]] for db_name, db in databases.items(): print_results(db.find(queries)) elif sys.argv[1] == '-i': db = PackageDatabase('local') db.update() for query in sys.argv[2:]: if db.has_package(query): print '%s is installed.' % query else: print '%s is NOT installed.' % query