Add option to hide one's email address
[aur.git] / git-interface / aurinfo.py
blobb286316f3eba5a1c269255c858a7472b3aceb093
1 #!/usr/bin/env python
3 from copy import copy, deepcopy
4 import pprint
5 import sys
7 class Attr(object):
8 def __init__(self, name, is_multivalued=False, allow_arch_extensions=False):
9 self.name = name
10 self.is_multivalued = is_multivalued
11 self.allow_arch_extensions = allow_arch_extensions
13 PKGBUILD_ATTRIBUTES = {
14 'arch': Attr('arch', True),
15 'backup': Attr('backup', True),
16 'changelog': Attr('changelog', False),
17 'checkdepends': Attr('checkdepends', True),
18 'conflicts': Attr('conflicts', True, True),
19 'depends': Attr('depends', True, True),
20 'epoch': Attr('epoch', False),
21 'groups': Attr('groups', True),
22 'install': Attr('install', False),
23 'license': Attr('license', True),
24 'makedepends': Attr('makedepends', True, True),
25 'md5sums': Attr('md5sums', True, True),
26 'noextract': Attr('noextract', True),
27 'optdepends': Attr('optdepends', True, True),
28 'options': Attr('options', True),
29 'pkgname': Attr('pkgname', False),
30 'pkgrel': Attr('pkgrel', False),
31 'pkgver': Attr('pkgver', False),
32 'provides': Attr('provides', True, True),
33 'replaces': Attr('replaces', True, True),
34 'sha1sums': Attr('sha1sums', True, True),
35 'sha224sums': Attr('sha224sums', True, True),
36 'sha256sums': Attr('sha256sums', True, True),
37 'sha384sums': Attr('sha384sums', True, True),
38 'sha512sums': Attr('sha512sums', True, True),
39 'source': Attr('source', True, True),
40 'url': Attr('url', False),
41 'validpgpkeys': Attr('validpgpkeys', True),
44 def find_attr(attrname):
45 # exact match
46 attr = PKGBUILD_ATTRIBUTES.get(attrname, None)
47 if attr:
48 return attr
50 # prefix match
51 # XXX: this could break in the future if PKGBUILD(5) ever
52 # introduces a key which is a subset of another.
53 for k in PKGBUILD_ATTRIBUTES.keys():
54 if attrname.startswith(k + '_'):
55 return PKGBUILD_ATTRIBUTES[k]
57 def IsMultiValued(attrname):
58 attr = find_attr(attrname)
59 return attr and attr.is_multivalued
61 class AurInfo(object):
62 def __init__(self):
63 self._pkgbase = {}
64 self._packages = {}
66 def GetPackageNames(self):
67 return self._packages.keys()
69 def GetMergedPackage(self, pkgname):
70 package = deepcopy(self._pkgbase)
71 package['pkgname'] = pkgname
72 for k, v in self._packages.get(pkgname).items():
73 package[k] = deepcopy(v)
74 return package
76 def AddPackage(self, pkgname):
77 self._packages[pkgname] = {}
78 return self._packages[pkgname]
80 def SetPkgbase(self, pkgbasename):
81 self._pkgbase = {'pkgname' : pkgbasename}
82 return self._pkgbase
85 class StderrECatcher(object):
86 def Catch(self, lineno, error):
87 print('ERROR[{:d}]: {:s}'.format(lineno, error), file=sys.stderr)
90 class CollectionECatcher(object):
91 def __init__(self):
92 self._errors = []
94 def Catch(self, lineno, error):
95 self._errors.append((lineno, error))
97 def HasErrors(self):
98 return len(self._errors) > 0
100 def Errors(self):
101 return copy(self._errors)
104 def ParseAurinfoFromIterable(iterable, ecatcher=None):
105 aurinfo = AurInfo()
107 if ecatcher is None:
108 ecatcher = StderrECatcher()
110 current_package = None
111 lineno = 0
113 for line in iterable:
114 lineno += 1
116 if line.startswith('#'):
117 continue
119 if not line.strip():
120 # end of package
121 current_package = None
122 continue
124 if not (line.startswith('\t') or line.startswith(' ')):
125 # start of new package
126 try:
127 key, value = map(str.strip, line.split('=', 1))
128 except ValueError:
129 ecatcher.Catch(lineno, 'unexpected header format in section={:s}'.format(
130 current_package['pkgname']))
131 continue
133 if key == 'pkgbase':
134 current_package = aurinfo.SetPkgbase(value)
135 elif key == 'pkgname':
136 current_package = aurinfo.AddPackage(value)
137 else:
138 ecatcher.Catch(lineno, 'unexpected new section not starting '
139 'with \'pkgname\' found')
140 continue
141 else:
142 # package attribute
143 if current_package is None:
144 ecatcher.Catch(lineno, 'package attribute found outside of '
145 'a package section')
146 continue
148 try:
149 key, value = map(str.strip, line.split('=', 1))
150 except ValueError:
151 ecatcher.Catch(lineno, 'unexpected attribute format in '
152 'section={:s}'.format(current_package['pkgname']))
154 if IsMultiValued(key):
155 if not current_package.get(key):
156 current_package[key] = []
157 if value:
158 current_package[key].append(value)
159 else:
160 if not current_package.get(key):
161 current_package[key] = value
162 else:
163 ecatcher.Catch(lineno, 'overwriting attribute '
164 '{:s}: {:s} -> {:s}'.format(key,
165 current_package[key], value))
167 return aurinfo
170 def ParseAurinfo(filename='.AURINFO', ecatcher=None):
171 with open(filename) as f:
172 return ParseAurinfoFromIterable(f, ecatcher)
175 def ValidateAurinfo(filename='.AURINFO'):
176 ecatcher = CollectionECatcher()
177 ParseAurinfo(filename, ecatcher)
178 errors = ecatcher.Errors()
179 for error in errors:
180 print('error on line {:d}: {:s}'.format(error), file=sys.stderr)
181 return not errors
184 if __name__ == '__main__':
185 pp = pprint.PrettyPrinter(indent=4)
187 if len(sys.argv) == 1:
188 print('error: not enough arguments')
189 sys.exit(1)
190 elif len(sys.argv) == 2:
191 action = sys.argv[1]
192 filename = '.AURINFO'
193 else:
194 action, filename = sys.argv[1:3]
196 if action == 'parse':
197 aurinfo = ParseAurinfo(filename)
198 for pkgname in aurinfo.GetPackageNames():
199 print(">>> merged package: {:s}".format(pkgname))
200 pp.pprint(aurinfo.GetMergedPackage(pkgname))
201 print()
202 elif action == 'validate':
203 sys.exit(not ValidateAurinfo(filename))
204 else:
205 print('unknown action: {:s}'.format(action))
206 sys.exit(1)
208 # vim: set et ts=4 sw=4: