diff options
author | Massimiliano Culpo <massimiliano.culpo@gmail.com> | 2023-09-28 18:21:52 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-28 16:21:52 +0000 |
commit | a236fce31f5d5018c788f51cda50492f1f241cba (patch) | |
tree | f8a8bfd3e6e32f3d8ebd60e1bf047e237d893314 /lib/spack/llnl | |
parent | f77a38a96bef7184f872f4225801ac937e825866 (diff) | |
download | spack-a236fce31f5d5018c788f51cda50492f1f241cba.tar.gz spack-a236fce31f5d5018c788f51cda50492f1f241cba.tar.bz2 spack-a236fce31f5d5018c788f51cda50492f1f241cba.tar.xz spack-a236fce31f5d5018c788f51cda50492f1f241cba.zip |
Partial removal of circular dependencies between `spack` and `llnl` (#40090)
Modifications:
- [x] Move `spack.util.string` to `llnl.string`
- [x] Remove dependency of `llnl` on `spack.error`
- [x] Move path of `spack.util.path` to `llnl.path`
- [x] Move `spack.util.environment.get_host_*` to `spack.spec`
Diffstat (limited to 'lib/spack/llnl')
-rw-r--r-- | lib/spack/llnl/path.py | 105 | ||||
-rw-r--r-- | lib/spack/llnl/string.py | 67 | ||||
-rw-r--r-- | lib/spack/llnl/util/filesystem.py | 6 | ||||
-rw-r--r-- | lib/spack/llnl/util/lock.py | 4 | ||||
-rw-r--r-- | lib/spack/llnl/util/symlink.py | 5 |
5 files changed, 179 insertions, 8 deletions
diff --git a/lib/spack/llnl/path.py b/lib/spack/llnl/path.py new file mode 100644 index 0000000000..bef9e14ada --- /dev/null +++ b/lib/spack/llnl/path.py @@ -0,0 +1,105 @@ +# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +"""Path primitives that just require Python standard library.""" +import functools +import sys +from typing import List, Optional +from urllib.parse import urlparse + + +class Path: + """Enum to identify the path-style.""" + + unix: int = 0 + windows: int = 1 + platform_path: int = windows if sys.platform == "win32" else unix + + +def format_os_path(path: str, mode: int = Path.unix) -> str: + """Formats the input path to use consistent, platform specific separators. + + Absolute paths are converted between drive letters and a prepended '/' as per platform + requirement. + + Parameters: + path: the path to be normalized, must be a string or expose the replace method. + mode: the path file separator style to normalize the passed path to. + Default is unix style, i.e. '/' + """ + if not path: + return path + if mode == Path.windows: + path = path.replace("/", "\\") + else: + path = path.replace("\\", "/") + return path + + +def convert_to_posix_path(path: str) -> str: + """Converts the input path to POSIX style.""" + return format_os_path(path, mode=Path.unix) + + +def convert_to_windows_path(path: str) -> str: + """Converts the input path to Windows style.""" + return format_os_path(path, mode=Path.windows) + + +def convert_to_platform_path(path: str) -> str: + """Converts the input path to the current platform's native style.""" + return format_os_path(path, mode=Path.platform_path) + + +def path_to_os_path(*parameters: str) -> List[str]: + """Takes an arbitrary number of positional parameters, converts each argument of type + string to use a normalized filepath separator, and returns a list of all values. + """ + + def _is_url(path_or_url: str) -> bool: + if "\\" in path_or_url: + return False + url_tuple = urlparse(path_or_url) + return bool(url_tuple.scheme) and len(url_tuple.scheme) > 1 + + result = [] + for item in parameters: + if isinstance(item, str) and not _is_url(item): + item = convert_to_platform_path(item) + result.append(item) + return result + + +def system_path_filter(_func=None, arg_slice: Optional[slice] = None): + """Filters function arguments to account for platform path separators. + Optional slicing range can be specified to select specific arguments + + This decorator takes all (or a slice) of a method's positional arguments + and normalizes usage of filepath separators on a per platform basis. + + Note: `**kwargs`, urls, and any type that is not a string are ignored + so in such cases where path normalization is required, that should be + handled by calling path_to_os_path directly as needed. + + Parameters: + arg_slice: a slice object specifying the slice of arguments + in the decorated method over which filepath separators are + normalized + """ + + def holder_func(func): + @functools.wraps(func) + def path_filter_caller(*args, **kwargs): + args = list(args) + if arg_slice: + args[arg_slice] = path_to_os_path(*args[arg_slice]) + else: + args = path_to_os_path(*args) + return func(*args, **kwargs) + + return path_filter_caller + + if _func: + return holder_func(_func) + return holder_func diff --git a/lib/spack/llnl/string.py b/lib/spack/llnl/string.py new file mode 100644 index 0000000000..a203be8d34 --- /dev/null +++ b/lib/spack/llnl/string.py @@ -0,0 +1,67 @@ +# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +"""String manipulation functions that do not have other dependencies than Python +standard library +""" +from typing import List, Optional + + +def comma_list(sequence: List[str], article: str = "") -> str: + if type(sequence) is not list: + sequence = list(sequence) + + if not sequence: + return "" + if len(sequence) == 1: + return sequence[0] + + out = ", ".join(str(s) for s in sequence[:-1]) + if len(sequence) != 2: + out += "," # oxford comma + out += " " + if article: + out += article + " " + out += str(sequence[-1]) + return out + + +def comma_or(sequence: List[str]) -> str: + """Return a string with all the elements of the input joined by comma, but the last + one (which is joined by 'or'). + """ + return comma_list(sequence, "or") + + +def comma_and(sequence: List[str]) -> str: + """Return a string with all the elements of the input joined by comma, but the last + one (which is joined by 'and'). + """ + return comma_list(sequence, "and") + + +def quote(sequence: List[str], q: str = "'") -> List[str]: + """Quotes each item in the input list with the quote character passed as second argument.""" + return [f"{q}{e}{q}" for e in sequence] + + +def plural(n: int, singular: str, plural: Optional[str] = None, show_n: bool = True) -> str: + """Pluralize <singular> word by adding an s if n != 1. + + Arguments: + n: number of things there are + singular: singular form of word + plural: optional plural form, for when it's not just singular + 's' + show_n: whether to include n in the result string (default True) + + Returns: + "1 thing" if n == 1 or "n things" if n != 1 + """ + number = f"{n} " if show_n else "" + if n == 1: + return f"{number}{singular}" + elif plural is not None: + return f"{number}{plural}" + else: + return f"{number}{singular}s" diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py index bd203ef200..8f4217049d 100644 --- a/lib/spack/llnl/util/filesystem.py +++ b/lib/spack/llnl/util/filesystem.py @@ -28,7 +28,8 @@ from llnl.util.lang import dedupe, memoized from llnl.util.symlink import islink, readlink, resolve_link_target_relative_to_the_link, symlink from spack.util.executable import Executable, which -from spack.util.path import path_to_os_path, system_path_filter + +from ..path import path_to_os_path, system_path_filter if sys.platform != "win32": import grp @@ -336,8 +337,7 @@ def filter_file( if string: regex = re.escape(regex) - filenames = path_to_os_path(*filenames) - for filename in filenames: + for filename in path_to_os_path(*filenames): msg = 'FILTER FILE: {0} [replacing "{1}"]' tty.debug(msg.format(filename, regex)) diff --git a/lib/spack/llnl/util/lock.py b/lib/spack/llnl/util/lock.py index 74a325acd2..156300b891 100644 --- a/lib/spack/llnl/util/lock.py +++ b/lib/spack/llnl/util/lock.py @@ -14,7 +14,7 @@ from typing import IO, Any, Callable, ContextManager, Dict, Generator, Optional, from llnl.util import lang, tty -import spack.util.string +from ..string import plural if sys.platform != "win32": import fcntl @@ -169,7 +169,7 @@ def _attempts_str(wait_time, nattempts): if nattempts <= 1: return "" - attempts = spack.util.string.plural(nattempts, "attempt") + attempts = plural(nattempts, "attempt") return " after {} and {}".format(lang.pretty_seconds(wait_time), attempts) diff --git a/lib/spack/llnl/util/symlink.py b/lib/spack/llnl/util/symlink.py index ab6654e847..4f4f965f13 100644 --- a/lib/spack/llnl/util/symlink.py +++ b/lib/spack/llnl/util/symlink.py @@ -11,8 +11,7 @@ import tempfile from llnl.util import lang, tty -from spack.error import SpackError -from spack.util.path import system_path_filter +from ..path import system_path_filter if sys.platform == "win32": from win32file import CreateHardLink @@ -338,7 +337,7 @@ def resolve_link_target_relative_to_the_link(link): return os.path.join(link_dir, target) -class SymlinkError(SpackError): +class SymlinkError(RuntimeError): """Exception class for errors raised while creating symlinks, junctions and hard links """ |