diff options
-rw-r--r-- | lib/spack/spack/cmd/edit.py | 119 | ||||
-rw-r--r-- | lib/spack/spack/test/cmd/edit.py | 46 | ||||
-rwxr-xr-x | share/spack/spack-completion.fish | 2 |
3 files changed, 105 insertions, 62 deletions
diff --git a/lib/spack/spack/cmd/edit.py b/lib/spack/spack/cmd/edit.py index 0134a522d8..e7d64c77ea 100644 --- a/lib/spack/spack/cmd/edit.py +++ b/lib/spack/spack/cmd/edit.py @@ -3,6 +3,7 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) +import errno import glob import os @@ -11,43 +12,13 @@ import llnl.util.tty as tty import spack.cmd import spack.paths import spack.repo -from spack.spec import Spec -from spack.util.editor import editor +import spack.util.editor description = "open package files in $EDITOR" section = "packaging" level = "short" -def edit_package(name, repo_path, namespace): - """Opens the requested package file in your favorite $EDITOR. - - Args: - name (str): The name of the package - repo_path (str): The path to the repository containing this package - namespace (str): A valid namespace registered with Spack - """ - # Find the location of the package - if repo_path: - repo = spack.repo.Repo(repo_path) - elif namespace: - repo = spack.repo.PATH.get_repo(namespace) - else: - repo = spack.repo.PATH - path = repo.filename_for_package_name(name) - - spec = Spec(name) - if os.path.exists(path): - if not os.path.isfile(path): - tty.die("Something is wrong. '{0}' is not a file!".format(path)) - if not os.access(path, os.R_OK): - tty.die("Insufficient permissions on '%s'!" % path) - else: - raise spack.repo.UnknownPackageError(spec.name) - - editor(path) - - def setup_parser(subparser): excl_args = subparser.add_mutually_exclusive_group() @@ -98,41 +69,67 @@ def setup_parser(subparser): excl_args.add_argument("-r", "--repo", default=None, help="path to repo to edit package in") excl_args.add_argument("-N", "--namespace", default=None, help="namespace of package to edit") - subparser.add_argument("package", nargs="?", default=None, help="package name") + subparser.add_argument("package", nargs="*", default=None, help="package name") -def edit(parser, args): - name = args.package +def locate_package(name: str, repo: spack.repo.Repo) -> str: + path = repo.filename_for_package_name(name) - # By default, edit package files - path = spack.paths.packages_path + try: + with open(path, "r"): + return path + except OSError as e: + if e.errno == errno.ENOENT: + raise spack.repo.UnknownPackageError(name) from e + tty.die(f"Cannot edit package: {e}") + + +def locate_file(name: str, path: str) -> str: + # convert command names to python module name + if path == spack.paths.command_path: + name = spack.cmd.python_name(name) + + file_path = os.path.join(path, name) + + # Try to open direct match. + try: + with open(file_path, "r"): + return file_path + except OSError as e: + if e.errno != errno.ENOENT: + tty.die(f"Cannot edit file: {e}") + pass + + # Otherwise try to find a file that starts with the name + candidates = glob.glob(file_path + "*") + exclude_list = [".pyc", "~"] # exclude binaries and backups + files = [f for f in candidates if not any(f.endswith(ext) for ext in exclude_list)] + if len(files) > 1: + tty.die( + f"Multiple files start with `{name}`:\n" + + "\n".join(f" {os.path.basename(f)}" for f in files) + ) + elif not files: + tty.die(f"No file for '{name}' was found in {path}") + return files[0] + + +def edit(parser, args): + names = args.package # If `--command`, `--test`, or `--module` is chosen, edit those instead if args.path: - path = args.path - if name: - # convert command names to python module name - if path == spack.paths.command_path: - name = spack.cmd.python_name(name) - - path = os.path.join(path, name) - if not os.path.exists(path): - files = glob.glob(path + "*") - exclude_list = [".pyc", "~"] # exclude binaries and backups - files = list(filter(lambda x: all(s not in x for s in exclude_list), files)) - if len(files) > 1: - m = "Multiple files exist with the name {0}.".format(name) - m += " Please specify a suffix. Files are:\n\n" - for f in files: - m += " " + os.path.basename(f) + "\n" - tty.die(m) - if not files: - tty.die("No file for '{0}' was found in {1}".format(name, path)) - path = files[0] # already confirmed only one entry in files - - editor(path) - elif name: - edit_package(name, args.repo, args.namespace) + paths = [locate_file(name, args.path) for name in names] if names else [args.path] + spack.util.editor.editor(*paths) + elif names: + if args.repo: + repo = spack.repo.Repo(args.repo) + elif args.namespace: + repo = spack.repo.PATH.get_repo(args.namespace) + else: + repo = spack.repo.PATH + paths = [locate_package(name, repo) for name in names] + spack.util.editor.editor(*paths) else: # By default open the directory where packages live - editor(path) + spack.util.editor.editor(spack.paths.packages_path) diff --git a/lib/spack/spack/test/cmd/edit.py b/lib/spack/spack/test/cmd/edit.py new file mode 100644 index 0000000000..dc367d2537 --- /dev/null +++ b/lib/spack/spack/test/cmd/edit.py @@ -0,0 +1,46 @@ +# Copyright 2013-2024 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) + +import os + +import spack.paths +import spack.repo +import spack.util.editor +from spack.build_systems import autotools, cmake +from spack.main import SpackCommand + +edit = SpackCommand("edit") + + +def test_edit_packages(monkeypatch, mock_packages: spack.repo.RepoPath): + """Test spack edit a b""" + path_a = mock_packages.filename_for_package_name("a") + path_b = mock_packages.filename_for_package_name("b") + called = False + + def editor(*args: str, **kwargs): + nonlocal called + called = True + assert args[0] == path_a + assert args[1] == path_b + + monkeypatch.setattr(spack.util.editor, "editor", editor) + edit("a", "b") + assert called + + +def test_edit_files(monkeypatch): + """Test spack edit --build-system autotools cmake""" + called = False + + def editor(*args: str, **kwargs): + nonlocal called + called = True + assert os.path.samefile(args[0], autotools.__file__) + assert os.path.samefile(args[1], cmake.__file__) + + monkeypatch.setattr(spack.util.editor, "editor", editor) + edit("--build-system", "autotools", "cmake") + assert called diff --git a/share/spack/spack-completion.fish b/share/spack/spack-completion.fish index 63abb4864e..4239faddc8 100755 --- a/share/spack/spack-completion.fish +++ b/share/spack/spack-completion.fish @@ -1451,7 +1451,7 @@ complete -c spack -n '__fish_spack_using_command docs' -s h -l help -d 'show thi # spack edit set -g __fish_spack_optspecs_spack_edit h/help b/build-system c/command d/docs t/test m/module r/repo= N/namespace= -complete -c spack -n '__fish_spack_using_command_pos 0 edit' -f -a '(__fish_spack_packages)' +complete -c spack -n '__fish_spack_using_command_pos_remainder 0 edit' -f -a '(__fish_spack_packages)' complete -c spack -n '__fish_spack_using_command edit' -s h -l help -f -a help complete -c spack -n '__fish_spack_using_command edit' -s h -l help -d 'show this help message and exit' complete -c spack -n '__fish_spack_using_command edit' -s b -l build-system -f -a path |