Source code for craft_parts.utils.path_utils

# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2023 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""Utility functions for paths."""
import re
from pathlib import PurePath, PurePosixPath
from typing import NamedTuple, Optional, TypeVar, Union, cast

from craft_parts.features import Features

FlexiblePath = TypeVar("FlexiblePath", PurePath, str)

HAS_PARTITION_REGEX = re.compile(r"^\([a-z]+\)(/.*)?$")


[docs]class PartitionPathPair(NamedTuple): """A pair containing a partition name and a path.""" partition: Optional[str] path: Union[PurePath, str]
def _has_partition(path: Union[PurePath, str]) -> bool: """Check whether a path has an explicit partition.""" return bool(HAS_PARTITION_REGEX.match(str(path)))
[docs]def get_partitioned_path(path: FlexiblePath) -> FlexiblePath: """Get a filepath compatible with the partitions feature. If the filepath begins with a partition, then the parentheses are stripped from the the partition. For example, `(default)/file` is converted to `default/file`. If the filepath does not begin with a partition, the `default` partition is prepended. For example, `file` is converted to `default/file`. :param path: The filepath to modify. :returns: A filepath that is compatible with the partitions feature. """ if not Features().enable_partitions: return path if str(path) == "*": return path partition, inner_path = get_partition_and_path(path) new_filepath = PurePosixPath(partition or ".", str(inner_path)) return path.__class__(new_filepath)
[docs]def get_partition_and_path(path: FlexiblePath) -> PartitionPathPair: """Break a partition path into the partition and the child path. If the path begins with a partition, that is used. Otherwise, the default partition is used. If partitions are not enabled, the partition will be None. :param path: The filepath to parse. :returns: A tuple of (partition, filepath) """ if not Features().enable_partitions: return PartitionPathPair(None, path) str_path = str(path) if _has_partition(str_path): if "/" not in str_path: return PartitionPathPair(str_path.strip("()"), cast(FlexiblePath, "")) partition, inner_path = str_path.split("/", maxsplit=1) return PartitionPathPair(partition.strip("()"), path.__class__(inner_path)) return PartitionPathPair("default", path)