diff --git a/osbuild/util/path.py b/osbuild/util/path.py index 9f5ef246..035b385f 100644 --- a/osbuild/util/path.py +++ b/osbuild/util/path.py @@ -2,7 +2,7 @@ import errno import os import os.path -from typing import Optional +from typing import Optional, Union from .ctx import suppress_oserror @@ -41,3 +41,18 @@ def in_tree(path: str, tree: str, must_exist: bool = False) -> bool: if path.startswith(tree): return not must_exist or os.path.exists(path) return False + + +def join_abs(root: Union[str, os.PathLike], *paths: Union[str, os.PathLike]) -> str: + """ + Join root and paths together, handling the case where paths are absolute paths. + In that case, paths are just appended to root as if they were relative paths. + The result is always an absolute path relative to the filesystem root '/'. + """ + final_path = root + for path in paths: + if os.path.isabs(path): + final_path = os.path.join(final_path, os.path.relpath(path, os.sep)) + else: + final_path = os.path.join(final_path, path) + return os.path.normpath(os.path.join(os.sep, final_path)) diff --git a/test/mod/test_util_path.py b/test/mod/test_util_path.py index 017671b2..8e3a29f7 100644 --- a/test/mod/test_util_path.py +++ b/test/mod/test_util_path.py @@ -68,3 +68,41 @@ def test_in_tree(): for args, expected in cases.items(): assert path.in_tree(*args) == expected, args + + +@pytest.mark.parametrize("test_case", ( + { + "args": ("",), + "expected": "/" + }, + { + "args": ("", "file"), + "expected": "/file" + }, + { + "args": ("", "/file"), + "expected": "/file" + }, + { + "args": ("/tmp", "/file"), + "expected": "/tmp/file" + }, + { + "args": ("/tmp", "/foo", "/bar"), + "expected": "/tmp/foo/bar" + }, + { + "args": ("/", "/foo"), + "expected": "/foo" + }, + { + "args": ("/", "/foo", "/bar"), + "expected": "/foo/bar" + }, + { + "args": (Path("/tmp"), "/foo", "/bar"), + "expected": "/tmp/foo/bar" + }, +)) +def test_join_abs(test_case): + assert path.join_abs(*test_case["args"]) == test_case["expected"]