# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-## Copyright 2023-2024 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."""importrefrompathlibimportPurePathfromtypingimportNamedTuple,TypeVarfromcraft_parts.errorsimportFeatureErrorfromcraft_parts.featuresimportFeaturesfromcraft_parts.utilsimportpartition_utilsFlexiblePath=TypeVar("FlexiblePath",PurePath,str)# regex for a path beginning with a (partition), like "(boot)/bin/sh"HAS_PARTITION_REGEX=re.compile(r"^\("+partition_utils.VALID_PARTITION_REGEX.pattern+r"\)(/.*)?$")# regex for a path beginning with a namespaced partition, like "(a/boot)/bin/sh"# Note that unlike HAS_PARTITION_REGEX, this one captures both namespaced partition and path.HAS_NAMESPACED_PARTITION_REGEX=re.compile(r"^(\("+partition_utils.VALID_NAMESPACED_PARTITION_REGEX.pattern+r"\))(/.*)?$")
[docs]classPartitionPathPair(NamedTuple):"""A pair containing a partition name and a path."""partition:str|Nonepath:PurePath|str
def_has_partition(path:PurePath|str)->bool:"""Check whether a path has an explicit partition."""returnbool(HAS_PARTITION_REGEX.match(str(path))orHAS_NAMESPACED_PARTITION_REGEX.match(str(path)))
[docs]defget_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) """ifnotFeatures().enable_partitions:returnPartitionPathPair(None,path)str_path=str(path)if_has_partition(str_path):partition,inner_path=_split_partition_and_inner_path(str_path)returnPartitionPathPair(partition.strip("()"),path.__class__(inner_path))returnPartitionPathPair("default",path)
def_split_partition_and_inner_path(str_path:str)->tuple[str,str]:"""Split a path with a partition into a partition and inner path. :param str_path: A string of the filepath beginning with a partition to split. :returns: A tuple containing the partition and inner path. If there is no inner path, the second element will be an empty string. :raises FeatureError: If `str_path` does not begin with a partition. """# split regular partitionsifre.match(HAS_PARTITION_REGEX,str_path):if"/"instr_path:# split into partition and inner_pathpartition,inner_path=str_path.split("/",maxsplit=1)# remove extra forward slashes between the partition and inner pathreturnpartition,inner_path.lstrip("/")returnstr_path,""# split namespaced partitionsmatch=re.match(HAS_NAMESPACED_PARTITION_REGEX,str_path)ifnotmatch:raiseFeatureError(f"Filepath {str_path!r} does not begin with a partition.")partition,inner_path=match.groups()# remove all forward slashes between the partition and the inner pathinner_path=(inner_pathor"").lstrip("/")returnpartition,inner_path