diff --git a/fdroidserver/lint.py b/fdroidserver/lint.py index fb35d9cb..f70097c4 100644 --- a/fdroidserver/lint.py +++ b/fdroidserver/lint.py @@ -63,10 +63,10 @@ http_checks = https_enforcings + http_url_shorteners + [ ] regex_checks = { - 'Web Site': http_checks, - 'Source Code': http_checks, + 'WebSite': http_checks, + 'SourceCode': http_checks, 'Repo': https_enforcings, - 'Issue Tracker': http_checks + [ + 'IssueTracker': http_checks + [ (re.compile(r'.*github\.com/[^/]+/[^/]+/*$'), "/issues is missing"), (re.compile(r'.*gitlab\.com/[^/]+/[^/]+/*$'), @@ -121,7 +121,7 @@ regex_checks = { def check_regexes(app): for f, checks in regex_checks.items(): for m, r in checks: - v = app.get_field(f) + v = app.get(f) t = metadata.fieldtype(f) if t == metadata.TYPE_MULTILINE: for l in v.splitlines(): @@ -183,8 +183,8 @@ def check_old_links(app): 'code.google.com', ] if any(s in app.Repo for s in usual_sites): - for f in ['Web Site', 'Source Code', 'Issue Tracker', 'Changelog']: - v = app.get_field(f) + for f in ['WebSite', 'SourceCode', 'IssueTracker', 'Changelog']: + v = app.get(f) if any(s in v for s in old_sites): yield "App is in '%s' but has a link to '%s'" % (app.Repo, v) @@ -241,7 +241,7 @@ def check_duplicates(app): links_seen = set() for f in ['Source Code', 'Web Site', 'Issue Tracker', 'Changelog']: - v = app.get_field(f) + v = app.get(f) if not v: continue v = v.lower() diff --git a/fdroidserver/metadata.py b/fdroidserver/metadata.py index 6fb69a48..2b091e2c 100644 --- a/fdroidserver/metadata.py +++ b/fdroidserver/metadata.py @@ -98,15 +98,21 @@ app_fields = set([ 'Current Version', 'Current Version Code', 'No Source Since', + 'Build', 'comments', # For formats that don't do inline comments 'builds', # For formats that do builds as a list ]) -class App(): +class App(dict): + + def __init__(self, copydict=None): + if copydict: + super().__init__(copydict) + return + super().__init__() - def __init__(self): self.Disabled = None self.AntiFeatures = [] self.Provides = None @@ -148,94 +154,21 @@ class App(): self.comments = {} self.added = None self.lastupdated = None - self._modified = set() - @classmethod - def field_to_attr(cls, f): - """ - Translates human-readable field names to attribute names, e.g. - 'Auto Name' to 'AutoName' - """ - return f.replace(' ', '') - - @classmethod - def attr_to_field(cls, k): - """ - Translates attribute names to human-readable field names, e.g. - 'AutoName' to 'Auto Name' - """ - if k in app_fields: - return k - f = re.sub(r'([a-z])([A-Z])', r'\1 \2', k) - return f - - def field_dict(self): - """ - Constructs an old-fashioned dict with the human-readable field - names. Should only be used for tests. - """ - d = {} - for k, v in self.__dict__.items(): - if k == 'builds': - d['builds'] = [] - for build in v: - b = {k: v for k, v in build.__dict__.items() if not k.startswith('_')} - d['builds'].append(b) - elif not k.startswith('_'): - f = App.attr_to_field(k) - d[f] = v - return d - - def get_field(self, f): - """Gets the value associated to a field name, e.g. 'Auto Name'""" - if f not in app_fields: - warn_or_exception('Unrecognised app field: ' + f) - k = App.field_to_attr(f) - return getattr(self, k) - - def set_field(self, f, v): - """Sets the value associated to a field name, e.g. 'Auto Name'""" - if f not in app_fields: - warn_or_exception('Unrecognised app field: ' + f) - k = App.field_to_attr(f) - self.__dict__[k] = v - self._modified.add(k) - - def append_field(self, f, v): - """Appends to the value associated to a field name, e.g. 'Auto Name'""" - if f not in app_fields: - warn_or_exception('Unrecognised app field: ' + f) - k = App.field_to_attr(f) - if k not in self.__dict__: - self.__dict__[k] = [v] + def __getattr__(self, name): + if name in self: + return self[name] else: - self.__dict__[k].append(v) + raise AttributeError("No such attribute: " + name) - def update_fields(self, d): - '''Like dict.update(), but using human-readable field names''' - for f, v in d.items(): - if f == 'builds': - for b in v: - build = Build() - build.update_flags(b) - self.builds.append(build) - else: - self.set_field(f, v) + def __setattr__(self, name, value): + self[name] = value - def update(self, d): - '''Like dict.update()''' - for k, v in d.__dict__.items(): - if k == '_modified': - continue - elif k == 'builds': - for b in v: - build = Build() - del(b.__dict__['_modified']) - build.update_flags(b.__dict__) - self.builds.append(build) - elif v: - self.__dict__[k] = v - self._modified.add(k) + def __delattr__(self, name): + if name in self: + del self[name] + else: + raise AttributeError("No such attribute: " + name) def get_last_build(self): if len(self.builds) > 0: @@ -256,16 +189,17 @@ TYPE_BUILD_V2 = 8 fieldtypes = { 'Description': TYPE_MULTILINE, - 'Maintainer Notes': TYPE_MULTILINE, + 'MaintainerNotes': TYPE_MULTILINE, 'Categories': TYPE_LIST, 'AntiFeatures': TYPE_LIST, - 'Build Version': TYPE_BUILD, + 'BuildVersion': TYPE_BUILD, 'Build': TYPE_BUILD_V2, - 'Use Built': TYPE_OBSOLETE, + 'UseBuilt': TYPE_OBSOLETE, } def fieldtype(name): + name = name.replace(' ', '') if name in fieldtypes: return fieldtypes[name] return TYPE_STRING @@ -518,9 +452,7 @@ valuetypes = { def check_metadata(app): for v in valuetypes: for k in v.fields: - if k not in app._modified: - continue - v.check(app.__dict__[k], app.id) + v.check(app[k], app.id) # Formatter for descriptions. Create an instance, and call parseline() with @@ -896,44 +828,21 @@ def sorted_builds(builds): esc_newlines = re.compile(r'\\( |\n)') -# This function uses __dict__ to be faster def post_metadata_parse(app): - - for k in app._modified: - v = app.__dict__[k] + # TODO keep native types, convert only for .txt metadata + for k, v in app.items(): if type(v) in (float, int): - app.__dict__[k] = str(v) + app[k] = str(v) builds = [] - for build in app.builds: - if not isinstance(build, Build): - build = Build(build) - builds.append(build) + if 'builds' in app: + for build in app['builds']: + if not isinstance(build, Build): + build = Build(build) + builds.append(build) - for k in build._modified: - v = build.__dict__[k] - if type(v) in (float, int): - build.__dict__[k] = str(v) - continue - ftype = flagtype(k) - - if ftype == TYPE_SCRIPT: - build.__dict__[k] = re.sub(esc_newlines, '', v).lstrip().rstrip() - elif ftype == TYPE_BOOL: - # TODO handle this using