_yaml.config_dump() for writing out config

This outputs YAML in a string that is suitable for use in regexps
and string replacements, as well as complete files.  It is therefore
explicitly set up to avoid writing out headers and footers.
This commit is contained in:
Hans-Christoph Steiner 2025-03-07 17:30:46 +01:00
parent 2f47938dbf
commit 3ab2baf542
4 changed files with 35 additions and 14 deletions

View File

@ -38,3 +38,27 @@ yaml = ruamel.yaml.YAML(typ='safe')
yaml.version = (1, 2) yaml.version = (1, 2)
yaml_dumper = ruamel.yaml.YAML(typ='rt') yaml_dumper = ruamel.yaml.YAML(typ='rt')
def config_dump(config, fp=None):
"""Dump config data in YAML 1.2 format without headers.
This outputs YAML in a string that is suitable for use in regexps
and string replacements, as well as complete files. It is therefore
explicitly set up to avoid writing out headers and footers.
This is modeled after PyYAML's yaml.dump(), which can dump to a file
or return a string.
https://yaml.dev/doc/ruamel.yaml/example/#Output_of_%60dump()%60_as_a_string
"""
dumper = ruamel.yaml.YAML(typ='rt')
dumper.default_flow_style = False
dumper.explicit_start = False
dumper.explicit_end = False
if fp is None:
with ruamel.yaml.compat.StringIO() as fp:
dumper.dump(config, fp)
return fp.getvalue()
dumper.dump(config, fp)

View File

@ -39,7 +39,6 @@ import sys
import re import re
import ast import ast
import gzip import gzip
import ruamel.yaml
import shutil import shutil
import stat import stat
import subprocess import subprocess
@ -67,7 +66,7 @@ from zipfile import ZipFile
import fdroidserver.metadata import fdroidserver.metadata
from fdroidserver import _ from fdroidserver import _
from fdroidserver._yaml import yaml, yaml_dumper from fdroidserver._yaml import yaml, config_dump
from fdroidserver.exception import FDroidException, VCSException, NoSubmodulesException, \ from fdroidserver.exception import FDroidException, VCSException, NoSubmodulesException, \
BuildException, VerificationException, MetaDataException BuildException, VerificationException, MetaDataException
from .asynchronousfilereader import AsynchronousFileReader from .asynchronousfilereader import AsynchronousFileReader
@ -4230,9 +4229,7 @@ def write_to_config(thisconfig, key, value=None):
lines[-1] += '\n' lines[-1] += '\n'
pattern = re.compile(r'^[\s#]*' + key + r':.*\n') pattern = re.compile(r'^[\s#]*' + key + r':.*\n')
with ruamel.yaml.compat.StringIO() as fp: repl = config_dump({key: value})
yaml_dumper.dump({key: value}, fp)
repl = fp.getvalue()
# If we replaced this line once, we make sure won't be a # If we replaced this line once, we make sure won't be a
# second instance of this line for this key in the document. # second instance of this line for this key in the document.

View File

@ -31,7 +31,7 @@ import fdroidserver.common
import fdroidserver.metadata import fdroidserver.metadata
from .shared_test_code import TmpCwd, mkdtemp from .shared_test_code import TmpCwd, mkdtemp
from fdroidserver.common import ANTIFEATURES_CONFIG_NAME, CATEGORIES_CONFIG_NAME from fdroidserver.common import ANTIFEATURES_CONFIG_NAME, CATEGORIES_CONFIG_NAME
from fdroidserver._yaml import yaml, yaml_dumper from fdroidserver._yaml import yaml, yaml_dumper, config_dump
from fdroidserver.exception import FDroidException, VCSException,\ from fdroidserver.exception import FDroidException, VCSException,\
MetaDataException, VerificationException MetaDataException, VerificationException
from fdroidserver.looseversion import LooseVersion from fdroidserver.looseversion import LooseVersion
@ -1933,7 +1933,7 @@ class CommonTest(unittest.TestCase):
os.chdir(self.tmpdir) os.chdir(self.tmpdir)
teststr = '/πÇÇ现代通用字-български-عربي1/ö/yml' teststr = '/πÇÇ现代通用字-български-عربي1/ö/yml'
with open(fdroidserver.common.CONFIG_FILE, 'w', encoding='utf-8') as fp: with open(fdroidserver.common.CONFIG_FILE, 'w', encoding='utf-8') as fp:
yaml_dumper.dump({'apksigner': teststr}, fp) config_dump({'apksigner': teststr}, fp)
self.assertTrue(os.path.exists(fdroidserver.common.CONFIG_FILE)) self.assertTrue(os.path.exists(fdroidserver.common.CONFIG_FILE))
config = fdroidserver.common.read_config() config = fdroidserver.common.read_config()
self.assertEqual(teststr, config.get('apksigner')) self.assertEqual(teststr, config.get('apksigner'))
@ -2633,7 +2633,7 @@ class CommonTest(unittest.TestCase):
' -providerClass sun.security.pkcs11.SunPKCS11' ' -providerClass sun.security.pkcs11.SunPKCS11'
' -providerArg opensc-fdroid.cfg' ' -providerArg opensc-fdroid.cfg'
} }
yaml_dumper.dump(d, fp) config_dump(d, fp)
config = fdroidserver.common.read_config() config = fdroidserver.common.read_config()
fdroidserver.common.config = config fdroidserver.common.config = config
self.assertTrue(isinstance(d['smartcardoptions'], str)) self.assertTrue(isinstance(d['smartcardoptions'], str))

View File

@ -12,7 +12,7 @@ from .shared_test_code import mkdtemp
import fdroidserver.common import fdroidserver.common
import fdroidserver.lint import fdroidserver.lint
import fdroidserver.metadata import fdroidserver.metadata
from fdroidserver._yaml import yaml_dumper from fdroidserver._yaml import config_dump
basedir = Path(__file__).parent basedir = Path(__file__).parent
@ -365,13 +365,13 @@ class LintTest(unittest.TestCase):
def test_lint_config_basic_mirrors_yml(self): def test_lint_config_basic_mirrors_yml(self):
os.chdir(self.testdir) os.chdir(self.testdir)
with Path('mirrors.yml').open('w') as fp: with Path('mirrors.yml').open('w') as fp:
yaml_dumper.dump([{'url': 'https://example.com/fdroid/repo'}], fp) config_dump([{'url': 'https://example.com/fdroid/repo'}], fp)
self.assertTrue(fdroidserver.lint.lint_config('mirrors.yml')) self.assertTrue(fdroidserver.lint.lint_config('mirrors.yml'))
def test_lint_config_mirrors_yml_kenya_countryCode(self): def test_lint_config_mirrors_yml_kenya_countryCode(self):
os.chdir(self.testdir) os.chdir(self.testdir)
with Path('mirrors.yml').open('w') as fp: with Path('mirrors.yml').open('w') as fp:
yaml_dumper.dump( config_dump(
[{'url': 'https://foo.com/fdroid/repo', 'countryCode': 'KE'}], fp [{'url': 'https://foo.com/fdroid/repo', 'countryCode': 'KE'}], fp
) )
self.assertTrue(fdroidserver.lint.lint_config('mirrors.yml')) self.assertTrue(fdroidserver.lint.lint_config('mirrors.yml'))
@ -380,7 +380,7 @@ class LintTest(unittest.TestCase):
"""WV is "indeterminately reserved" so it should never be used.""" """WV is "indeterminately reserved" so it should never be used."""
os.chdir(self.testdir) os.chdir(self.testdir)
with Path('mirrors.yml').open('w') as fp: with Path('mirrors.yml').open('w') as fp:
yaml_dumper.dump( config_dump(
[{'url': 'https://foo.com/fdroid/repo', 'countryCode': 'WV'}], fp [{'url': 'https://foo.com/fdroid/repo', 'countryCode': 'WV'}], fp
) )
self.assertFalse(fdroidserver.lint.lint_config('mirrors.yml')) self.assertFalse(fdroidserver.lint.lint_config('mirrors.yml'))
@ -389,7 +389,7 @@ class LintTest(unittest.TestCase):
"""Only ISO 3166-1 alpha 2 are supported""" """Only ISO 3166-1 alpha 2 are supported"""
os.chdir(self.testdir) os.chdir(self.testdir)
with Path('mirrors.yml').open('w') as fp: with Path('mirrors.yml').open('w') as fp:
yaml_dumper.dump( config_dump(
[{'url': 'https://de.com/fdroid/repo', 'countryCode': 'DEU'}], fp [{'url': 'https://de.com/fdroid/repo', 'countryCode': 'DEU'}], fp
) )
self.assertFalse(fdroidserver.lint.lint_config('mirrors.yml')) self.assertFalse(fdroidserver.lint.lint_config('mirrors.yml'))
@ -398,7 +398,7 @@ class LintTest(unittest.TestCase):
"""WV is "indeterminately reserved" so it should never be used.""" """WV is "indeterminately reserved" so it should never be used."""
os.chdir(self.testdir) os.chdir(self.testdir)
with Path('mirrors.yml').open('w') as fp: with Path('mirrors.yml').open('w') as fp:
yaml_dumper.dump( config_dump(
[ [
{'url': 'https://bar.com/fdroid/repo', 'countryCode': 'BA'}, {'url': 'https://bar.com/fdroid/repo', 'countryCode': 'BA'},
{'url': 'https://foo.com/fdroid/repo', 'countryCode': 'FO'}, {'url': 'https://foo.com/fdroid/repo', 'countryCode': 'FO'},