summaryrefslogtreecommitdiffstats
path: root/scripts/git-integration/aurinfo.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/git-integration/aurinfo.py')
-rw-r--r--scripts/git-integration/aurinfo.py204
1 files changed, 204 insertions, 0 deletions
diff --git a/scripts/git-integration/aurinfo.py b/scripts/git-integration/aurinfo.py
new file mode 100644
index 00000000..d9b93729
--- /dev/null
+++ b/scripts/git-integration/aurinfo.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python
+
+from copy import copy, deepcopy
+import pprint
+import sys
+
+class Attr(object):
+ def __init__(self, name, is_multivalued=False, allow_arch_extensions=False):
+ self.name = name
+ self.is_multivalued = is_multivalued
+ self.allow_arch_extensions = allow_arch_extensions
+
+PKGBUILD_ATTRIBUTES = {
+ 'arch': Attr('arch', True),
+ 'backup': Attr('backup', True),
+ 'changelog': Attr('changelog', False),
+ 'checkdepends': Attr('checkdepends', True),
+ 'conflicts': Attr('conflicts', True, True),
+ 'depends': Attr('depends', True, True),
+ 'epoch': Attr('epoch', False),
+ 'groups': Attr('groups', True),
+ 'install': Attr('install', False),
+ 'license': Attr('license', True),
+ 'makedepends': Attr('makedepends', True, True),
+ 'md5sums': Attr('md5sums', True, True),
+ 'noextract': Attr('noextract', True),
+ 'optdepends': Attr('optdepends', True, True),
+ 'options': Attr('options', True),
+ 'pkgname': Attr('pkgname', False),
+ 'pkgrel': Attr('pkgrel', False),
+ 'pkgver': Attr('pkgver', False),
+ 'provides': Attr('provides', True, True),
+ 'replaces': Attr('replaces', True, True),
+ 'sha1sums': Attr('sha1sums', True, True),
+ 'sha224sums': Attr('sha224sums', True, True),
+ 'sha256sums': Attr('sha256sums', True, True),
+ 'sha384sums': Attr('sha384sums', True, True),
+ 'sha512sums': Attr('sha512sums', True, True),
+ 'source': Attr('source', True, True),
+ 'url': Attr('url', False),
+ 'validpgpkeys': Attr('validpgpkeys', True),
+}
+
+def find_attr(attrname):
+ # exact match
+ attr = PKGBUILD_ATTRIBUTES.get(attrname, None)
+ if attr:
+ return attr
+
+ # prefix match
+ # XXX: this could break in the future if PKGBUILD(5) ever
+ # introduces a key which is a subset of another.
+ for k in PKGBUILD_ATTRIBUTES.keys():
+ if attrname.startswith(k + '_'):
+ return PKGBUILD_ATTRIBUTES[k]
+
+def IsMultiValued(attrname):
+ attr = find_attr(attrname)
+ return attr and attr.is_multivalued
+
+class AurInfo(object):
+ def __init__(self):
+ self._pkgbase = {}
+ self._packages = {}
+
+ def GetPackageNames(self):
+ return self._packages.keys()
+
+ def GetMergedPackage(self, pkgname):
+ package = deepcopy(self._pkgbase)
+ package['pkgname'] = pkgname
+ for k, v in self._packages.get(pkgname).items():
+ package[k] = deepcopy(v)
+ return package
+
+ def AddPackage(self, pkgname):
+ self._packages[pkgname] = {}
+ return self._packages[pkgname]
+
+ def SetPkgbase(self, pkgbasename):
+ self._pkgbase = {'pkgname' : pkgbasename}
+ return self._pkgbase
+
+
+class StderrECatcher(object):
+ def Catch(self, lineno, error):
+ print('ERROR[%d]: %s' % (lineno, error), file=sys.stderr)
+
+
+class CollectionECatcher(object):
+ def __init__(self):
+ self._errors = []
+
+ def Catch(self, lineno, error):
+ self._errors.append((lineno, error))
+
+ def HasErrors(self):
+ return len(self._errors) > 0
+
+ def Errors(self):
+ return copy(self._errors)
+
+
+def ParseAurinfoFromIterable(iterable, ecatcher=None):
+ aurinfo = AurInfo()
+
+ if ecatcher is None:
+ ecatcher = StderrECatcher()
+
+ current_package = None
+ lineno = 0
+
+ for line in iterable:
+ lineno += 1
+
+ if line.startswith('#'):
+ continue
+
+ if not line.strip():
+ # end of package
+ current_package = None
+ continue
+
+ if not line.startswith('\t'):
+ # start of new package
+ try:
+ key, value = map(str.strip, line.split('=', 1))
+ except ValueError:
+ ecatcher.Catch(lineno, 'unexpected header format in section=%s' %
+ current_package['pkgname'])
+ continue
+
+ if key == 'pkgbase':
+ current_package = aurinfo.SetPkgbase(value)
+ else:
+ current_package = aurinfo.AddPackage(value)
+ else:
+ # package attribute
+ if current_package is None:
+ ecatcher.Catch(lineno, 'package attribute found outside of '
+ 'a package section')
+ continue
+
+ try:
+ key, value = map(str.strip, line.split('=', 1))
+ except ValueError:
+ ecatcher.Catch(lineno, 'unexpected attribute format in '
+ 'section=%s' % current_package['pkgname'])
+
+ if IsMultiValued(key):
+ if not current_package.get(key):
+ current_package[key] = []
+ if value:
+ current_package[key].append(value)
+ else:
+ if not current_package.get(key):
+ current_package[key] = value
+ else:
+ ecatcher.Catch(lineno, 'overwriting attribute '
+ '%s: %s -> %s' % (key, current_package[key],
+ value))
+
+ return aurinfo
+
+
+def ParseAurinfo(filename='.AURINFO', ecatcher=None):
+ with open(filename) as f:
+ return ParseAurinfoFromIterable(f, ecatcher)
+
+
+def ValidateAurinfo(filename='.AURINFO'):
+ ecatcher = CollectionECatcher()
+ ParseAurinfo(filename, ecatcher)
+ errors = ecatcher.Errors()
+ for error in errors:
+ print('error on line %d: %s' % error, file=sys.stderr)
+ return not errors
+
+
+if __name__ == '__main__':
+ pp = pprint.PrettyPrinter(indent=4)
+
+ if len(sys.argv) == 1:
+ print('error: not enough arguments')
+ sys.exit(1)
+ elif len(sys.argv) == 2:
+ action = sys.argv[1]
+ filename = '.AURINFO'
+ else:
+ action, filename = sys.argv[1:3]
+
+ if action == 'parse':
+ aurinfo = ParseAurinfo()
+ for pkgname in aurinfo.GetPackageNames():
+ print(">>> merged package: %s" % pkgname)
+ pp.pprint(aurinfo.GetMergedPackage(pkgname))
+ print()
+ elif action == 'validate':
+ sys.exit(not ValidateAurinfo(filename))
+ else:
+ print('unknown action: %s' % action)
+ sys.exit(1)
+
+# vim: set et ts=4 sw=4: