f2/nix: provide a function to serialise python objects to nix code

Change-Id: I863029263edbc701a18b18da4b58fcb35f575614
This commit is contained in:
rootile
2026-04-19 10:10:22 +00:00
committed by Rutile
parent 1e986c81ab
commit 62b728e766
2 changed files with 74 additions and 1 deletions
+36
View File
@@ -16,8 +16,11 @@ from testlib.fixtures.command import CommandResult, Command
from testlib.fixtures.env import ManagedEnv
from testlib.utils import is_value_of_type
from textwrap import dedent
type _NixSettingValue = str | int | list[str] | bool | None
type _NixValue = str | int | float | list[_NixValue] | dict[str, _NixValue] | bool | None
def _serialise_config(value: _NixSettingValue) -> str:
@@ -32,6 +35,39 @@ def _serialise_config(value: _NixSettingValue) -> str:
raise ValueError(msg)
def serialise_nix(value: _NixValue) -> str:
"""
Serialises the given python object into a nix represenatation.
NOTE: this interface does not allow accessing variables unless using interpolation, as all strings will be surrounded by quotes.
"""
def escape(v: str) -> str:
if "\\r" in v:
raise ValueError("\\r is not supported for conversion")
escaped = v.replace("\\", "\\\\").replace("$", "\\$").replace('"', '\\"')
return f'"{escaped}"'
if is_value_of_type(value, list[_NixValue.__value__]):
return f"[{' '.join([serialise_nix(v) for v in value])}]"
if is_value_of_type(value, dict[str, _NixValue.__value__]):
return dedent(f"""
{{
{"\n ".join([f"{escape(k)} = {serialise_nix(v)};" for k, v in value.items()])}
}}
""")
if is_value_of_type(value, bool):
return "true" if value else "false"
if is_value_of_type(value, int | float):
return str(value)
if is_value_of_type(value, str):
return escape(value)
if is_value_of_type(value, None):
return "null"
msg = f"Value is unsupported in nix code: {value!r}"
raise ValueError(msg)
class NixSettings:
"""Settings for invoking Nix"""
+38 -1
View File
@@ -3,7 +3,9 @@ from pathlib import Path
import pytest
from testlib.fixtures.env import ManagedEnv
from testlib.fixtures.nix import NixSettings
from testlib.fixtures.nix import NixSettings, serialise_nix
from textwrap import dedent
def test_nix_settings_set_item():
@@ -115,3 +117,38 @@ def test_nix_settings_to_env_overlay_no_store_dir(tmp_path: Path):
settings.to_env_overlay(env)
assert "store = local?root=/some/path\n" in env._env["NIX_CONFIG"]
class TestSerialiseNix:
def test_list(self):
assert serialise_nix(["a", 1, None]) == '["a" 1 null]'
def test_dict(self):
assert serialise_nix(
{"a": 1, "b": None, "c": "hello world", "d": 3.14159265, "e": True}
) == dedent("""
{
"a" = 1;
"b" = null;
"c" = "hello world";
"d" = 3.14159265;
"e" = true;
}
""")
def test_escaping(self):
assert serialise_nix({'"a': 'Hello ""', "1": "${foo}", "${foo}": None}) == dedent("""
{
"\\"a" = "Hello \\"\\"";
"1" = "\\${foo}";
"\\${foo}" = null;
}
""")
def test_quotes(self):
assert serialise_nix({"a b": None, "\\n": '${awa}"'}) == dedent("""
{
"a b" = null;
"\\\\n" = "\\${awa}\\"";
}
""")