commands: add support for paged output
Signed-off-by: Todd Gamblin <tgamblin@llnl.gov>
This commit is contained in:
parent
b932c14008
commit
81a1f97779
@ -178,6 +178,10 @@ config:
|
||||
package_lock_timeout: null
|
||||
|
||||
|
||||
# pager(s) to use for commands with potentially long output (e.g., spack info)
|
||||
pager:
|
||||
- less -FXRS
|
||||
|
||||
# Control how shared libraries are located at runtime on Linux. See the
|
||||
# the Spack documentation for details.
|
||||
shared_linking:
|
||||
|
@ -4,12 +4,14 @@
|
||||
|
||||
import argparse
|
||||
import difflib
|
||||
import functools
|
||||
import importlib
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import Counter
|
||||
from typing import List, Optional, Union
|
||||
from typing import Callable, List, Optional, Union
|
||||
|
||||
import llnl.string
|
||||
import llnl.util.tty as tty
|
||||
@ -30,6 +32,7 @@
|
||||
import spack.store
|
||||
import spack.traverse as traverse
|
||||
import spack.user_environment as uenv
|
||||
import spack.util.executable as exe
|
||||
import spack.util.spack_json as sjson
|
||||
import spack.util.spack_yaml as syaml
|
||||
|
||||
@ -723,3 +726,123 @@ def __init__(self, cmd_name):
|
||||
long_msg += "\n ".join(similar)
|
||||
|
||||
super().__init__(msg, long_msg)
|
||||
|
||||
|
||||
def find_pager(pager_candidates: List[str]) -> Optional[List[str]]:
|
||||
"""Find a pager from spack configuration.
|
||||
|
||||
Arguments:
|
||||
pager_candidates: list of candidate commands with optional arguments, e.g. "less -FXRS"
|
||||
|
||||
Returns:
|
||||
Arguments, including the found command, to launch the pager, or None if not found.
|
||||
"""
|
||||
for pager in pager_candidates:
|
||||
# split each string in the list of pagers into args
|
||||
argv = pager.split()
|
||||
if not argv:
|
||||
continue
|
||||
|
||||
# try to find the requested pager command
|
||||
command, *args = argv
|
||||
path = exe.which_string(command)
|
||||
if not path:
|
||||
continue
|
||||
|
||||
# return the execv args we need to launch this thing
|
||||
return [command] + args
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def spack_pager_candidates() -> List[str]:
|
||||
"""Get a list of pager candidates by consulting environment and config.
|
||||
|
||||
Order of precedence is:
|
||||
|
||||
1. ``SPACK_PAGER``: pager just for spack
|
||||
2. ``PAGER``: user's preferred pager, from their environment
|
||||
3. ``config:pager``: list of pager candidates in config
|
||||
|
||||
"""
|
||||
pager_candidates = []
|
||||
|
||||
spack_pager = os.environ.get("SPACK_PAGER")
|
||||
if spack_pager:
|
||||
pager_candidates.append(spack_pager)
|
||||
|
||||
pager = os.environ.get("PAGER")
|
||||
if pager:
|
||||
pager_candidates.append(pager)
|
||||
|
||||
config_pagers = spack.config.get("config:pager")
|
||||
if config_pagers:
|
||||
pager_candidates.extend(config_pagers)
|
||||
|
||||
return pager_candidates
|
||||
|
||||
|
||||
def paged(command_function: Callable) -> Callable:
|
||||
"""Decorator for commands whose output should be sent to a pager by default.
|
||||
|
||||
This will launch a subprocess for, e.g., ``less``, and will redirect ``stdout`` to it, as
|
||||
``git`` does for commands like ``git log``.
|
||||
|
||||
The command will attempt to maintain colored output while paging, so you need a pager
|
||||
that supports color, like ``less -R``. Spack defaults to using ``less -FXRS`` if it's
|
||||
found, and nothing if not. You probably *do not* want to use ``more`` or any other
|
||||
non-color-capable pager.
|
||||
"""
|
||||
pager_execv_args = find_pager(spack_pager_candidates())
|
||||
if not pager_execv_args:
|
||||
return command_function
|
||||
|
||||
@functools.wraps(command_function)
|
||||
def wrapper(*args, **kwargs):
|
||||
# figure out if we're running the command with --help
|
||||
is_help = False
|
||||
if args and isinstance(args[-1], argparse.Namespace):
|
||||
is_help = args[-1].help
|
||||
|
||||
# don't page if not a tty, and don't page help output
|
||||
if not sys.stdout.isatty() or is_help:
|
||||
return command_function(*args, **kwargs)
|
||||
|
||||
# Flush any buffered output before redirection.
|
||||
sys.stdout.flush()
|
||||
|
||||
# save original stdout and original color setting
|
||||
original_stdout_fd = os.dup(sys.stdout.fileno())
|
||||
original_stdout_isatty = sys.stdout.isatty
|
||||
|
||||
# launch the pager
|
||||
proc = subprocess.Popen(pager_execv_args, stdin=subprocess.PIPE)
|
||||
|
||||
try:
|
||||
# Redirect stdout's file descriptor to the pager's stdin.
|
||||
os.dup2(proc.stdin.fileno(), sys.stdout.fileno())
|
||||
|
||||
# make spack think the pager is a tty
|
||||
sys.stdout.isatty = lambda: True
|
||||
|
||||
# run the decorated function
|
||||
result = command_function(*args, **kwargs)
|
||||
|
||||
# Flush any remaining output.
|
||||
sys.stdout.flush()
|
||||
|
||||
return result
|
||||
|
||||
finally:
|
||||
# quit cheating on isatty
|
||||
sys.stdout.isatty = original_stdout_isatty
|
||||
|
||||
# restore stdout
|
||||
os.dup2(original_stdout_fd, sys.stdout.fileno())
|
||||
os.close(original_stdout_fd)
|
||||
|
||||
# Close the pager's stdin and wait for it to finish.
|
||||
proc.stdin.close()
|
||||
proc.wait()
|
||||
|
||||
return wrapper
|
||||
|
@ -10,6 +10,7 @@
|
||||
import llnl.util.filesystem as fs
|
||||
import llnl.util.tty as tty
|
||||
|
||||
import spack.cmd
|
||||
import spack.config
|
||||
import spack.environment as ev
|
||||
import spack.error
|
||||
@ -169,6 +170,7 @@ def print_flattened_configuration(*, blame: bool) -> None:
|
||||
syaml.dump_config(flattened, stream=sys.stdout, default_flow_style=False, blame=blame)
|
||||
|
||||
|
||||
@spack.cmd.paged
|
||||
def config_get(args):
|
||||
"""Dump merged YAML configuration for a specific section.
|
||||
|
||||
@ -178,6 +180,7 @@ def config_get(args):
|
||||
print_configuration(args, blame=False)
|
||||
|
||||
|
||||
@spack.cmd.paged
|
||||
def config_blame(args):
|
||||
"""Print out line-by-line blame of merged YAML."""
|
||||
print_configuration(args, blame=True)
|
||||
|
@ -363,6 +363,7 @@ def _find_query(args, env):
|
||||
return results, concretized_but_not_installed
|
||||
|
||||
|
||||
@cmd.paged
|
||||
def find(parser, args):
|
||||
env = ev.active_environment()
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
from llnl.util.tty.colify import colify
|
||||
|
||||
import spack.builder
|
||||
import spack.cmd
|
||||
import spack.deptypes as dt
|
||||
import spack.fetch_strategy as fs
|
||||
import spack.install_test
|
||||
@ -481,6 +482,7 @@ def print_licenses(pkg, args):
|
||||
color.cprint(line)
|
||||
|
||||
|
||||
@spack.cmd.paged
|
||||
def info(parser, args):
|
||||
spec = spack.spec.Spec(args.package)
|
||||
pkg_cls = spack.repo.PATH.get_pkg_class(spec.fullname)
|
||||
|
@ -15,6 +15,7 @@
|
||||
import llnl.util.tty as tty
|
||||
from llnl.util.tty.colify import colify
|
||||
|
||||
import spack.cmd
|
||||
import spack.deptypes as dt
|
||||
import spack.package_base
|
||||
import spack.repo
|
||||
@ -315,6 +316,7 @@ def head(n, span_id, title, anchor=None):
|
||||
out.write("</div>\n")
|
||||
|
||||
|
||||
@spack.cmd.paged
|
||||
def list(parser, args):
|
||||
# retrieve the formatter to use from args
|
||||
formatter = formatters[args.format]
|
||||
|
@ -374,6 +374,12 @@ def make_argument_parser(**kwargs):
|
||||
choices=("always", "never", "auto"),
|
||||
help="when to colorize output (default: auto)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-pager",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="do not run any output through a pager",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--config",
|
||||
@ -536,6 +542,10 @@ def setup_main_options(args):
|
||||
if args.timestamp:
|
||||
tty.set_timestamp(True)
|
||||
|
||||
# override pager configuration (note ::)
|
||||
if args.no_pager:
|
||||
spack.config.set("config::pager", [], scope="command_line")
|
||||
|
||||
# override lock configuration if passed on command line
|
||||
if args.locks is not None:
|
||||
if args.locks is False:
|
||||
|
@ -104,6 +104,7 @@
|
||||
"additional_external_search_paths": {"type": "array", "items": {"type": "string"}},
|
||||
"binary_index_ttl": {"type": "integer", "minimum": 0},
|
||||
"aliases": {"type": "object", "patternProperties": {r"\w[\w-]*": {"type": "string"}}},
|
||||
"pager": {"type": "array", "items": {"type": "string"}},
|
||||
},
|
||||
"deprecatedProperties": [
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user