import logging
import textwrap
import io
-import ruamel.yaml
import yaml
# use libyaml if it is available
try:
YamlLoader = Loader
import fdroidserver.common
-from fdroidserver.exception import MetaDataException
+from fdroidserver.exception import MetaDataException, FDroidException
srclibs = None
warnings_action = None
'commit',
'subdir',
'submodules',
+ 'sudo',
'init',
'patch',
'gradle',
'gradleprops',
'antcommands',
'novcheck',
+ 'antifeatures',
]
# old .txt format has version name/code inline in the 'Build:' line
self.commit = None
self.subdir = None
self.submodules = False
+ self.sudo = ''
self.init = ''
self.patch = []
self.gradle = []
self.gradleprops = []
self.antcommands = []
self.novcheck = False
+ self.antifeatures = []
if copydict:
super().__init__(copydict)
return
'gradle': TYPE_LIST,
'antcommands': TYPE_LIST,
'gradleprops': TYPE_LIST,
+ 'sudo': TYPE_SCRIPT,
'init': TYPE_SCRIPT,
'prebuild': TYPE_SCRIPT,
'build': TYPE_SCRIPT,
'forceversion': TYPE_BOOL,
'forcevercode': TYPE_BOOL,
'novcheck': TYPE_BOOL,
+ 'antifeatures': TYPE_LIST,
}
# Generic value types
valuetypes = {
- FieldValidator("Hexadecimal",
- r'^[0-9a-f]+$',
+ FieldValidator("Flattr ID",
+ r'^[0-9a-z]+$',
['FlattrID']),
FieldValidator("HTTP link",
["ArchivePolicy"]),
FieldValidator("Anti-Feature",
- r'^(Ads|Tracking|NonFreeNet|NonFreeDep|NonFreeAdd|UpstreamNonFree|NonFreeAssets|KnownVuln)$',
+ r'^(Ads|Tracking|NonFreeNet|NonFreeDep|NonFreeAdd|UpstreamNonFree|NonFreeAssets|KnownVuln|ApplicationDebuggable)$',
["AntiFeatures"]),
FieldValidator("Auto Update Mode",
for metadatapath in sorted(glob.glob(os.path.join('metadata', '*.txt'))
+ glob.glob(os.path.join('metadata', '*.json'))
+ glob.glob(os.path.join('metadata', '*.yml'))
+ + glob.glob('.fdroid.txt')
+ glob.glob('.fdroid.json')
+ glob.glob('.fdroid.yml')):
packageName, _ = fdroidserver.common.get_extension(os.path.basename(metadatapath))
if type(v) in (float, int):
app[k] = str(v)
+ if 'Builds' in app:
+ app['builds'] = app.pop('Builds')
+
+ if 'flavours' in app and app['flavours'] == [True]:
+ app['flavours'] = 'yes'
+
if isinstance(app.Categories, str):
app.Categories = [app.Categories]
elif app.Categories is None:
else:
app.Categories = [str(i) for i in app.Categories]
+ def _yaml_bool_unmapable(v):
+ return v in (True, False, [True], [False])
+
+ def _yaml_bool_unmap(v):
+ if v is True:
+ return 'yes'
+ elif v is False:
+ return 'no'
+ elif v == [True]:
+ return ['yes']
+ elif v == [False]:
+ return ['no']
+
+ _bool_allowed = ('disable', 'kivy', 'maven', 'buildozer')
+
builds = []
if 'builds' in app:
for build in app['builds']:
if not isinstance(build, Build):
build = Build(build)
for k, v in build.items():
- if flagtype(k) == TYPE_LIST:
- if isinstance(v, str):
- build[k] = [v]
- elif isinstance(v, bool):
- if v:
- build[k] = ['yes']
+ if not (v is None):
+ if flagtype(k) == TYPE_LIST:
+ if _yaml_bool_unmapable(v):
+ build[k] = _yaml_bool_unmap(v)
+
+ if isinstance(v, str):
+ build[k] = [v]
+ elif isinstance(v, bool):
+ if v:
+ build[k] = ['yes']
+ else:
+ build[k] = []
+ elif flagtype(k) is TYPE_INT:
+ build[k] = str(v)
+ elif flagtype(k) is TYPE_STRING:
+ if isinstance(v, bool) and k in _bool_allowed:
+ build[k] = v
else:
- build[k] = []
- elif flagtype(k) == TYPE_STRING and type(v) in (float, int):
- build[k] = str(v)
+ if _yaml_bool_unmapable(v):
+ build[k] = _yaml_bool_unmap(v)
+ else:
+ build[k] = str(v)
builds.append(build)
app.builds = sorted_builds(builds)
def parse_yaml_metadata(mf, app):
-
- yamlinfo = yaml.load(mf, Loader=YamlLoader)
- app.update(yamlinfo)
+ yamldata = yaml.load(mf, Loader=YamlLoader)
+ if yamldata:
+ app.update(yamldata)
return app
def write_yaml(mf, app):
+ # import rumael.yaml and check version
+ try:
+ import ruamel.yaml
+ except ImportError as e:
+ raise FDroidException('ruamel.yaml not instlled, can not write metadata.') from e
+ if not ruamel.yaml.__version__:
+ raise FDroidException('ruamel.yaml.__version__ not accessible. Please make sure a ruamel.yaml >= 0.13 is installed..')
+ m = re.match('(?P<major>[0-9]+)\.(?P<minor>[0-9]+)\.(?P<patch>[0-9]+)(-.+)?',
+ ruamel.yaml.__version__)
+ if not m:
+ raise FDroidException('ruamel.yaml version malfored, please install an upstream version of ruamel.yaml')
+ if int(m.group('major')) < 0 or int(m.group('minor')) < 13:
+ raise FDroidException('currently installed version of ruamel.yaml ({}) is too old, >= 1.13 required.'.format(ruamel.yaml.__version__))
+ # suiteable version ruamel.yaml imported successfully
+
+ _yaml_bools_true = ('y', 'Y', 'yes', 'Yes', 'YES',
+ 'true', 'True', 'TRUE',
+ 'on', 'On', 'ON')
+ _yaml_bools_false = ('n', 'N', 'no', 'No', 'NO',
+ 'false', 'False', 'FALSE',
+ 'off', 'Off', 'OFF')
+ _yaml_bools_plus_lists = []
+ _yaml_bools_plus_lists.extend(_yaml_bools_true)
+ _yaml_bools_plus_lists.extend([[x] for x in _yaml_bools_true])
+ _yaml_bools_plus_lists.extend(_yaml_bools_false)
+ _yaml_bools_plus_lists.extend([[x] for x in _yaml_bools_false])
+
def _class_as_dict_representer(dumper, data):
'''Creates a YAML representation of a App/Build instance'''
return dumper.represent_dict(data)
def _field_to_yaml(typ, value):
if typ is TYPE_STRING:
+ if value in _yaml_bools_plus_lists:
+ return ruamel.yaml.scalarstring.SingleQuotedScalarString(str(value))
return str(value)
elif typ is TYPE_INT:
return int(value)
# next iteration will need to insert a newline
insert_newline = True
else:
- if (hasattr(app, field) and getattr(app, field)) or field is 'Builds':
+ if app.get(field) or field is 'Builds':
+ # .txt calls it 'builds' internally, everywhere else its 'Builds'
if field is 'Builds':
- cm.update({field: _builds_to_yaml(app)})
+ if app.get('builds'):
+ cm.update({field: _builds_to_yaml(app)})
elif field is 'CurrentVersionCode':
cm.update({field: _field_to_yaml(TYPE_INT, getattr(app, field))})
else:
b = ruamel.yaml.comments.CommentedMap()
for field in fields:
if hasattr(build, field) and getattr(build, field):
- b.update({field: _field_to_yaml(flagtype(field), getattr(build, field))})
+ value = getattr(build, field)
+ if field == 'gradle' and value == ['off']:
+ value = [ruamel.yaml.scalarstring.SingleQuotedScalarString('off')]
+ if field in ('disable', 'kivy', 'maven', 'buildozer'):
+ if value == 'no':
+ continue
+ elif value == 'yes':
+ value = 'yes'
+ b.update({field: _field_to_yaml(flagtype(field), value)})
builds.append(b)
# insert extra empty lines between build entries
yaml_app_field_order = [
'Disabled',
- 'AnitFeatures',
+ 'AntiFeatures',
'Provides',
'Categories',
'License',
warn_or_exception('Cannot write "%s", not an accepted format, use: %s'
% (metadatapath, ', '.join(accepted)))
- with open(metadatapath, 'w', encoding='utf8') as mf:
- if ext == 'txt':
- return write_txt(mf, app)
- elif ext == 'yml':
- return write_yaml(mf, app)
+ try:
+ with open(metadatapath, 'w', encoding='utf8') as mf:
+ if ext == 'txt':
+ return write_txt(mf, app)
+ elif ext == 'yml':
+ return write_yaml(mf, app)
+ except FDroidException as e:
+ os.remove(metadatapath)
+ raise e
+
warn_or_exception('Unknown metadata format: %s' % metadatapath)