diff --git a/.flake8 b/.flake8 index f551319e..dd862b7c 100644 --- a/.flake8 +++ b/.flake8 @@ -15,7 +15,7 @@ extend-ignore = exclude = .git, - .venv, + .venv*, __pycache__, build, dist, diff --git a/CHANGES.md b/CHANGES.md index 81943472..3f658bed 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,15 @@ development source code and as such may not be routinely kept up to date. # __NEXT__ +## Documentation + +* The command-line `--help` output for commands and the corresponding + documentation pages on the web are more integrated and improved in various + small ways. In particular, command options are more cross-referencable and + directly linkable and the wrap-width of `--help` output is more consistent + and reliably readable. + ([#299](https://github.com/nextstrain/cli/pull/299)) + # 7.1.0 (22 June 2023) diff --git a/devel/generate-command-doc b/devel/generate-command-doc new file mode 100755 index 00000000..34ba1083 --- /dev/null +++ b/devel/generate-command-doc @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +""" +Generate documentation pages for each Nextstrain CLI command. + +Uses the Sphinx ``program`` and ``option`` directives so they can be +cross-referenced. An alternative to using the ``autodoc`` or ``autoprogram`` +extensions which dynamically generate rST at Sphinx build time instead. + +To generate rST files for all commands:: + + ./devel/generate-command-doc + +Files are only written if their contents need updating. The paths of the +updated files are output to stderr. + +To instead output (to stdout) the rST for a single command:: + + ./devel/generate-command-doc nextstrain build + +This can be useful for development loops. +""" +import os +from argparse import ArgumentParser, SUPPRESS, _SubParsersAction +from contextlib import contextmanager, redirect_stdout +from difflib import diff_bytes, unified_diff +from hashlib import md5 +from inspect import cleandoc +from pathlib import Path +from sys import exit, stdout, stderr +from tempfile import TemporaryDirectory +from textwrap import dedent, indent +from typing import Iterable, Tuple, Union + +doc = (Path(__file__).resolve().parent.parent / "doc/").relative_to(Path.cwd()) +tmp = TemporaryDirectory() + +# Force some environment vars before loading any Nextstrain CLI code which may +# inspect them, for reproducible/stable output. +os.environ.update({ + # The argparse HelpFormatter cares about terminal size, but we don't want + # that to vary based on where we run this program. + "COLUMNS": "1000", + + # Avoid the current user's personal configuration from affecting output. + "NEXTSTRAIN_HOME": tmp.name, + + # Ensure we detect a browser for stable `nextstrain view` output. + "BROWSER": "/bin/true", +}) + +from nextstrain.cli import make_parser +from nextstrain.cli.argparse import HelpFormatter, walk_commands, OPTIONS_TITLE +from nextstrain.cli.debug import debug + + +# We generate docs for these and they can be directly accessed, but they're +# unlinked so we must note that to suppress warnings from Sphinx. +hidden = { + "nextstrain", + "nextstrain debugger", + "nextstrain init-shell", +} + + +argparser = ArgumentParser( + prog = "./devel/generate-command-doc", + usage = "./devel/generate-command-doc [--check] [ […]]", + description = __doc__, + formatter_class = HelpFormatter) + +argparser.add_argument("command_given", nargs = "*", metavar = "", help = "The single command, given as multiple arguments, for which to generate rST.") +argparser.add_argument("--check", action = "store_true", help = "Only check if any file contents have changed; do not update any files. Exits 1 if there are changes, 0 if not.") +argparser.add_argument("--diff", action = "store_true", help = "Show a diff for files that change (or would change, if --check is also specified).") + + +def main(*, command_given = None, check = False, diff = False): + if check and command_given: + print("error: --check is only supported when updating files for all commands", file = stderr) + return 1 + + if diff and command_given: + print("error: --diff is only supported when updating files for all commands", file = stderr) + return 1 + + check_failed = False + command_given = tuple(command_given) + + for command, parser in walk_commands(make_parser()): + if command_given and command != command_given: + continue + + page = command_page(command, parser) + path = doc / f"{page}.rst" + + debug(f"--> {' '.join(command)}") + debug(f"Generating rST…") + + rst = generate_rst(command, parser) + + if command_given: + print(rst) + else: + new_rst = rst.encode("utf-8") + old_rst = path.read_bytes() if path.exists() else None + + new_md5 = md5(new_rst).hexdigest() + old_md5 = md5(old_rst).hexdigest() if old_rst is not None else "0" * 32 + + debug(f"Old MD5: {old_md5}") + debug(f"New MD5: {new_md5}") + + if old_md5 != new_md5: + if check: + check_failed = True + else: + path.write_bytes(new_rst) + debug(f"wrote {len(new_rst):,} bytes ({new_md5}) to {path}") + print(path, file = stderr) + + if diff: + stdout.writelines( + unified_diff( + old_rst.decode("utf-8").splitlines(keepends = True) if old_rst is not None else [], + new_rst.decode("utf-8").splitlines(keepends = True), + str(path), + str(path), + old_md5, + new_md5)) + + else: + debug(f"{path} unchanged") + + return 1 if check and check_failed else 0 + + +def command_page(command: Tuple[str, ...], parser: ArgumentParser) -> str: + return ( + "commands/" + + "/".join(command[1:]) + + ("/index" if parser._subparsers else "")) + + +def generate_rst(command: Tuple[str, ...], parser: ArgumentParser) -> str: + return "\n".join(chunk or "" for chunk in _generate_rst(command, parser)) + + +def _generate_rst(command: Tuple[str, ...], parser: ArgumentParser) -> Iterable[Union[str, None]]: + program = " ".join(command) + formatter = parser.formatter_class(program) + usage = parser.format_usage() + description = cleandoc(parser.description or "") + epilog = cleandoc(parser.epilog or "") + + if program in hidden: + yield ":orphan:" + yield + + yield ".. default-role:: literal" + yield + yield ".. role:: command-reference(ref)" + yield + yield f".. program:: {program}" + yield + yield f".. _{program}:" + yield + yield "=" * len(program) + yield program + yield "=" * len(program) + yield + yield ".. code-block:: none" + yield + yield indent(usage, " ") + yield + yield description + yield + + for group in parser._action_groups: + if not group._group_actions: + continue + + title = group.title if group.title and group.title != OPTIONS_TITLE else "options" + description = cleandoc(group.description or "") + + yield title + yield "=" * len(title) + yield + yield description + yield + + for action in group._group_actions: + if action.help is SUPPRESS: + continue + + if isinstance(action, _SubParsersAction): + for choice in action._choices_actions: + subcommand = choice.dest + subparser = action.choices[subcommand] + subpage = command_page((*command, subcommand), subparser) + yield f".. option:: {subcommand}" + yield + yield indent(f"{choice.help}. See :doc:`/{subpage}`.", " ") + yield + else: + invocation = formatter._format_action_invocation(action) + description = (action.help or "") % {"default": action.default} + + yield f".. option:: {invocation}" + yield + yield indent(description, " ") + yield + + yield epilog + + +if __name__ == "__main__": + exit(main(**vars(argparser.parse_args()))) diff --git a/devel/setup-venv b/devel/setup-venv index 9f44986a..4eb05334 100755 --- a/devel/setup-venv +++ b/devel/setup-venv @@ -6,6 +6,6 @@ venv="$base/.venv" set -x rm -rf "$venv" -python3.6 -m venv "$venv" +python3 -m venv "$venv" "$venv"/bin/pip install --upgrade pip setuptools wheel pip-tools "$venv"/bin/pip install -e '.[dev]' diff --git a/doc/Makefile b/doc/Makefile index 7f50af56..2ab5a5ce 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -1,5 +1,6 @@ # Minimal makefile for Sphinx documentation # +SHELL := bash -euo pipefail # You can set these variables from the command line, and also # from the environment for the first two. @@ -8,16 +9,33 @@ SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build +# Require stricter builds with +# -n: warn on missing references +# -W: error on warnings +# --keep-going: find all warnings +# https://www.sphinx-doc.org/en/master/man/sphinx-build.html +STRICT = -n -W --keep-going +LOOSE = -n + +GENERATE = ../devel/generate-command-doc + # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -.PHONY: help Makefile +.PHONY: help Makefile livehtml # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + if [[ $@ != clean ]]; then $(GENERATE); fi + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(STRICT) $(SPHINXOPTS) $(O) + +HOST ?= 127.0.0.1 +PORT ?= 8000 + +serve: dirhtml + cd "$(BUILDDIR)/dirhtml" && python3 -m http.server --bind "$(HOST)" "$(PORT)" livehtml: - sphinx-autobuild -b dirhtml "$(SOURCEDIR)" "$(BUILDDIR)/dirhtml" $(SPHINXOPTS) $(O) + sphinx-autobuild -b dirhtml "$(SOURCEDIR)" "$(BUILDDIR)/dirhtml" --host "$(HOST)" --port "$(PORT)" --watch ../nextstrain/cli --pre-build "$(GENERATE)" $(LOOSE) $(SPHINXOPTS) $(O) diff --git a/doc/commands/authorization.rst b/doc/commands/authorization.rst index 2dd5878b..2161f4b2 100644 --- a/doc/commands/authorization.rst +++ b/doc/commands/authorization.rst @@ -1,9 +1,38 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain authorization + +.. _nextstrain authorization: + ======================== nextstrain authorization ======================== -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: authorization +.. code-block:: none + + usage: nextstrain authorization [-h] + + +Produce an Authorization header appropriate for nextstrain.org's web API. + +This is a development tool unnecessary for normal usage. It's useful for +directly making API requests to nextstrain.org with `curl` or similar +commands. For example:: + + curl -si https://nextstrain.org/whoami \ + --header "Accept: application/json" \ + --header @<(nextstrain authorization) + +Exits with an error if no one is logged in. + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + diff --git a/doc/commands/build.rst b/doc/commands/build.rst index 93feef1b..bf3a48f8 100644 --- a/doc/commands/build.rst +++ b/doc/commands/build.rst @@ -1,9 +1,205 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain build + +.. _nextstrain build: + ================ nextstrain build ================ -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: build +.. code-block:: none + + usage: nextstrain build [options] [...] + nextstrain build --help + + +Runs a pathogen build in a Nextstrain runtime. + +The build directory should contain a Snakefile, which will be run with +snakemake. + +You need at least one runtime available to run a build. You can test if the +Docker, Conda, ambient, or AWS Batch runtimes are properly supported on your +computer by running:: + + nextstrain check-setup + +The `nextstrain build` command is designed to cleanly separate the Nextstrain +build interface from provisioning a runtime environment, so that running builds +is as easy as possible. It also lets us more seamlessly make runtime +changes in the future as desired or necessary. + +positional arguments +==================== + + + +.. option:: + + Path to pathogen build directory + +.. option:: ... + + Additional arguments to pass to the executed program + +options +======= + + + +.. option:: --help, -h + + Show a brief help message of common options and exit + +.. option:: --help-all + + Show a full help message of all options and exit + +.. option:: --detach + + Run the build in the background, detached from your terminal. Re-attach later using :option:`--attach`. Currently only supported when also using :option:`--aws-batch`. + +.. option:: --attach + + Re-attach to a :option:`--detach`'ed build to view output and download results. Currently only supported when also using :option:`--aws-batch`. + +.. option:: --cpus + + Number of CPUs/cores/threads/jobs to utilize at once. Limits containerized (Docker, AWS Batch) builds to this amount. Informs Snakemake's resource scheduler when applicable. Informs the AWS Batch instance size selection. By default, no constraints are placed on how many CPUs are used by a build; builds may use all that are available if they're able to. + +.. option:: --memory + + Amount of memory to make available to the build. Units of b, kb, mb, gb, kib, mib, gib are supported. Limits containerized (Docker, AWS Batch) builds to this amount. Informs Snakemake's resource scheduler when applicable. Informs the AWS Batch instance size selection. + +.. option:: --download + + Only download new or modified files matching ```` from the + remote build. Shell-style advanced globbing is supported, but be + sure to escape wildcards or quote the whole pattern so your shell + doesn't expand them. May be passed more than once. Currently only + supported when also using :option:`--aws-batch`. Default is to + download every new or modified file. + + Besides basic glob features like single-part wildcards (``*``), + character classes (``[…]``), and brace expansion (``{…, …}``), + several advanced globbing features are also supported: multi-part + wildcards (``**``), extended globbing (``@(…)``, ``+(…)``, etc.), + and negation (``!…``). + + + + +.. option:: --no-download + + Do not download any files from the remote build when it completes. Currently only supported when also using :option:`--aws-batch`. + +.. option:: --no-logs + + Do not show the log messages of the remote build. Currently only supported when also using :option:`--aws-batch`. Default is to show all log messages, even when attaching to a completed build. + +runtime selection options +========================= + +Select the Nextstrain runtime to use, if the +default is not suitable. + +.. option:: --docker + + Run commands inside a container image using Docker. (default) + +.. option:: --conda + + Run commands with access to a fully-managed Conda environment. + +.. option:: --singularity + + Run commands inside a container image using Singularity. + +.. option:: --ambient + + Run commands in the ambient environment, outside of any container image. + +.. option:: --aws-batch + + Run commands remotely on AWS Batch inside the Nextstrain container image. + +runtime options +=============== + +Options shared by all runtimes. + +.. option:: --env [=] + + Set the environment variable ```` to the value in the current environment (i.e. pass it thru) or to the given ````. May be specified more than once. Overrides any variables of the same name set via :option:`--envdir`. When this option or :option:`--envdir` is given, the default behaviour of automatically passing thru several "well-known" variables is disabled. The "well-known" variables are ``AUGUR_RECURSION_LIMIT``, ``AUGUR_MINIFY_JSON``, ``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``, ``AWS_SESSION_TOKEN``, ``ID3C_URL``, ``ID3C_USERNAME``, ``ID3C_PASSWORD``, ``RETHINK_HOST``, and ``RETHINK_AUTH_KEY``. Pass those variables explicitly via :option:`--env` or :option:`--envdir` if you need them in combination with other variables. + +.. option:: --envdir + + Set environment variables from the envdir at ````. May be specified more than once. An envdir is a directory containing files describing environment variables. Each filename is used as the variable name. The first line of the contents of each file is used as the variable value. When this option or :option:`--env` is given, the default behaviour of automatically passing thru several "well-known" variables is disabled. See the description of :option:`--env` for more details. + +development options +=================== + +These should generally be unnecessary unless you're developing Nextstrain. + +.. option:: --image + + Container image name to use for the Nextstrain runtime (default: nextstrain/base for Docker and AWS Batch, docker://nextstrain/base for Singularity) + +.. option:: --exec + + Program to run inside the runtime + +development options for --docker +================================ + + + +.. option:: --augur + + Replace the image's copy of augur with a local copy + +.. option:: --auspice + + Replace the image's copy of auspice with a local copy + +.. option:: --fauna + + Replace the image's copy of fauna with a local copy + +.. option:: --sacra + + Replace the image's copy of sacra with a local copy + +.. option:: --docker-arg ... + + Additional arguments to pass to `docker run` + +development options for --aws-batch +=================================== + +See +for more information. + +.. option:: --aws-batch-job + + Name of the AWS Batch job definition to use + +.. option:: --aws-batch-queue + + Name of the AWS Batch job queue to use + +.. option:: --aws-batch-s3-bucket + + Name of the AWS S3 bucket to use as shared storage + +.. option:: --aws-batch-cpus + + Number of vCPUs to request for job + +.. option:: --aws-batch-memory + + Amount of memory in MiB to request for job + diff --git a/doc/commands/check-setup.rst b/doc/commands/check-setup.rst index bfb01829..f504c9c2 100644 --- a/doc/commands/check-setup.rst +++ b/doc/commands/check-setup.rst @@ -1,9 +1,74 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain check-setup + +.. _nextstrain check-setup: + ====================== nextstrain check-setup ====================== -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: check-setup +.. code-block:: none + + usage: nextstrain check-setup [--set-default] [ [ ...]] + nextstrain check-setup --help + + +Checks for supported runtimes. + +Five runtimes are tested by default: + + • Our Docker image is the preferred runtime. Docker itself must + be installed and configured on your computer first, but once it is, the + runtime is robust and reproducible. + + • Our Conda runtime will be tested for existence and appearance of + completeness. This runtime is more isolated and reproducible than your + ambient runtime, but is less isolated and robust than the Docker + runtime. + + • Our Singularity runtime uses the same container image as our Docker + runtime. Singularity must be installed and configured on your computer + first, although it is often already present on HPC systems. This runtime + is more isolated and reproducible than the Conda runtime, but potentially + less so than the Docker runtime. + + • Your ambient setup will be tested for snakemake, augur, and auspice. + Their presence implies a working runtime, but does not guarantee + it. + + • Remote jobs on AWS Batch. Your AWS account, if credentials are available + in your environment or via aws-cli configuration, will be tested for the + presence of appropriate resources. Their presence implies a working AWS + Batch runtime, but does not guarantee it. + +Provide one or more runtime names as arguments to test just those instead. + +Exits with an error code if the default runtime (docker) is not +supported or, when the default runtime is omitted from checks, if none of the +checked runtimes are supported. + +positional arguments +==================== + + + +.. option:: + + The Nextstrain runtimes to check. (default: docker, conda, singularity, ambient, aws-batch) + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + +.. option:: --set-default + + Set the default runtime to the first which passes check-setup. Checks run in the order given, if any, otherwise in the default order: docker, conda, singularity, ambient, aws-batch. + diff --git a/doc/commands/debugger.rst b/doc/commands/debugger.rst new file mode 100644 index 00000000..bb535bab --- /dev/null +++ b/doc/commands/debugger.rst @@ -0,0 +1,32 @@ +:orphan: + +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain debugger + +.. _nextstrain debugger: + +=================== +nextstrain debugger +=================== + +.. code-block:: none + + usage: nextstrain debugger [-h] + + +Launch pdb from within the Nextstrain CLI process. + +This is a development and troubleshooting tool unnecessary for normal usage. + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + diff --git a/doc/commands/deploy.rst b/doc/commands/deploy.rst index 04d3aa60..08f18484 100644 --- a/doc/commands/deploy.rst +++ b/doc/commands/deploy.rst @@ -1,9 +1,66 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain deploy + +.. _nextstrain deploy: + ================= nextstrain deploy ================= -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: deploy +.. code-block:: none + + usage: nextstrain deploy [-h] [--dry-run] [ […]] [ [ […]] ...] + + +Upload dataset and narratives files to a remote destination. + + +The `nextstrain deploy` command is an alias for `nextstrain remote upload`. + + +A remote destination URL specifies where to upload, e.g. to upload the dataset +files:: + + auspice/ncov_local.json + auspice/ncov_local_root-sequence.json + auspice/ncov_local_tip-frequencies.json + +so they're visible at `https://nextstrain.org/groups/example/ncov`:: + + nextstrain remote upload nextstrain.org/groups/example/ncov auspice/ncov_local*.json + +If uploading multiple datasets or narratives, uploading to the top-level of a +Nextstrain Group, or uploading to an S3 remote, then the local filenames are +used in combination with any path prefix in the remote source URL. + +See :command-reference:`nextstrain remote` for more information on remote sources. + +positional arguments +==================== + + + +.. option:: + + Remote destination URL for a dataset or narrative. A path prefix if the files to upload comprise more than one dataset or narrative or the remote is S3. + +.. option:: [ […]] + + Files to upload. Typically dataset and sidecar files (Auspice JSON files) and/or narrative files (Markdown files). + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + +.. option:: --dry-run + + Don't actually upload anything, just show what would be uploaded + diff --git a/doc/commands/index.rst b/doc/commands/index.rst new file mode 100644 index 00000000..ab9057e7 --- /dev/null +++ b/doc/commands/index.rst @@ -0,0 +1,102 @@ +:orphan: + +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain + +.. _nextstrain: + +========== +nextstrain +========== + +.. code-block:: none + + usage: nextstrain [-h] {build,view,deploy,remote,shell,update,setup,check-setup,login,logout,whoami,version,init-shell,authorization,debugger} ... + + +Nextstrain command-line interface (CLI) + +The `nextstrain` program and its subcommands aim to provide a consistent way to +run and visualize pathogen builds and access Nextstrain components like Augur +and Auspice across computing platforms such as Docker, Conda, and AWS Batch. + +Run `nextstrain --help` for usage information about each command. +See <:doc:`/index`> for more documentation. + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + +commands +======== + + + +.. option:: build + + Run pathogen build. See :doc:`/commands/build`. + +.. option:: view + + View pathogen builds and narratives. See :doc:`/commands/view`. + +.. option:: deploy + + Deploy pathogen build. See :doc:`/commands/deploy`. + +.. option:: remote + + Upload, download, and manage remote datasets and narratives.. See :doc:`/commands/remote/index`. + +.. option:: shell + + Start a new shell in a runtime. See :doc:`/commands/shell`. + +.. option:: update + + Update a runtime. See :doc:`/commands/update`. + +.. option:: setup + + Set up a runtime. See :doc:`/commands/setup`. + +.. option:: check-setup + + Check runtime setups. See :doc:`/commands/check-setup`. + +.. option:: login + + Log into Nextstrain.org. See :doc:`/commands/login`. + +.. option:: logout + + Log out of Nextstrain.org. See :doc:`/commands/logout`. + +.. option:: whoami + + Show information about the logged-in user. See :doc:`/commands/whoami`. + +.. option:: version + + Show version information. See :doc:`/commands/version`. + +.. option:: init-shell + + Print shell init script. See :doc:`/commands/init-shell`. + +.. option:: authorization + + Print an HTTP Authorization header. See :doc:`/commands/authorization`. + +.. option:: debugger + + Start a debugger. See :doc:`/commands/debugger`. + diff --git a/doc/commands/init-shell.rst b/doc/commands/init-shell.rst new file mode 100644 index 00000000..8c8ea199 --- /dev/null +++ b/doc/commands/init-shell.rst @@ -0,0 +1,48 @@ +:orphan: + +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain init-shell + +.. _nextstrain init-shell: + +===================== +nextstrain init-shell +===================== + +.. code-block:: none + + usage: nextstrain init-shell [-h] [shell] + + +Prints the shell init script for a Nextstrain CLI standalone installation. + +If PATH does not contain the expected installation path, emits an appropriate +``export PATH=…`` statement. Otherwise, emits only a comment. + +Use this command in your shell config with a line like the following:: + + eval "$(…/path/to/nextstrain init-shell)" + +Exits with error if run in an non-standalone installation. + +positional arguments +==================== + + + +.. option:: shell + + Shell that's being initialized (e.g. bash, zsh, etc.); currently we always emit POSIX shell syntax but this may change in the future. + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + diff --git a/doc/commands/login.rst b/doc/commands/login.rst index eeb9836c..40133cd3 100644 --- a/doc/commands/login.rst +++ b/doc/commands/login.rst @@ -1,9 +1,65 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain login + +.. _nextstrain login: + ================ nextstrain login ================ -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: login +.. code-block:: none + + usage: nextstrain login [-h] [--username ] [--no-prompt] [--renew] + + +Log into Nextstrain.org and save credentials for later use. + +The first time you log in, you'll be prompted for your Nextstrain.org username +and password. After that, locally-saved authentication tokens will be used and +automatically renewed as needed when you run other `nextstrain` commands +requiring log in. You can also re-run this `nextstrain login` command to force +renewal if you want. You'll only be prompted for your username and password if +the locally-saved tokens are unable to be renewed or missing entirely. + +If you log out of Nextstrain.org on other devices/clients (like your web +browser), you may be prompted to re-enter your username and password by this +command sooner than usual. + +Your password itself is never saved locally. + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + +.. option:: --username , -u + + The username to log in as. If not provided, the :envvar:`NEXTSTRAIN_USERNAME` environment variable will be used if available, otherwise you'll be prompted to enter your username. + +.. option:: --no-prompt + + Never prompt for a username/password; succeed only if there are login credentials in the environment or existing valid/renewable tokens saved locally, otherwise error. Useful for scripting. + +.. option:: --renew + + Renew existing tokens, if possible. Useful to refresh group membership information (for example) sooner than the tokens would normally be renewed. + +For automation purposes, you may opt to provide environment variables instead +of interactive input and/or command-line options: + +.. envvar:: NEXTSTRAIN_USERNAME + + Username on nextstrain.org. Ignored if :option:`--username` is also + provided. + +.. envvar:: NEXTSTRAIN_PASSWORD + + Password for nextstrain.org user. Required if :option:`--no-prompt` is + used without existing valid/renewable tokens. \ No newline at end of file diff --git a/doc/commands/logout.rst b/doc/commands/logout.rst index a8fd07e5..a6a981f2 100644 --- a/doc/commands/logout.rst +++ b/doc/commands/logout.rst @@ -1,9 +1,34 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain logout + +.. _nextstrain logout: + ================= nextstrain logout ================= -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: logout +.. code-block:: none + + usage: nextstrain logout [-h] + + +Log out of Nextstrain.org by deleting locally-saved credentials. + +The authentication tokens are removed but not invalidated, so if you used them +outside of the `nextstrain` command, they will remain valid until they expire. + +Other devices/clients (like your web browser) are not logged out of +Nextstrain.org. + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + diff --git a/doc/commands/remote/delete.rst b/doc/commands/remote/delete.rst index 5d385aca..6f67c53f 100644 --- a/doc/commands/remote/delete.rst +++ b/doc/commands/remote/delete.rst @@ -1,9 +1,58 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain remote delete + +.. _nextstrain remote delete: + ======================== nextstrain remote delete ======================== -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: remote delete +.. code-block:: none + + usage: nextstrain remote delete [--recursively] + nextstrain remote delete --help + + +Delete datasets and narratives on a remote source. + +A remote source URL specifies what to delete, e.g. to delete the "beta-cov" +dataset in the Nextstrain Group "blab":: + + nextstrain remote delete nextstrain.org/groups/blab/beta-cov + +The :option:`--recursively` option allows for deleting multiple datasets or narratives +at once, e.g. to delete all the "ncov/wa/…" datasets in the "blab" group:: + + nextstrain remote delete --recursively nextstrain.org/groups/blab/ncov/wa + +See :command-reference:`nextstrain remote` for more information on remote sources. + +positional arguments +==================== + + + +.. option:: + + Remote source URL for a dataset or narrative. A path prefix to scope/filter by if using :option:`--recursively`. + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + +.. option:: --recursively, -r + + Delete everything under the given remote URL path prefix + +.. option:: --dry-run + + Don't actually delete anything, just show what would be deleted + diff --git a/doc/commands/remote/download.rst b/doc/commands/remote/download.rst index 8934d7e3..3dfb0b79 100644 --- a/doc/commands/remote/download.rst +++ b/doc/commands/remote/download.rst @@ -1,9 +1,80 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain remote download + +.. _nextstrain remote download: + ========================== nextstrain remote download ========================== -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: remote download +.. code-block:: none + + usage: nextstrain remote download [] + nextstrain remote download --recursively [] + nextstrain remote download --help + + +Download datasets and narratives from a remote source. + +A remote source URL specifies what to download, e.g. to download one of the +seasonal influenza datasets:: + + nextstrain remote download nextstrain.org/flu/seasonal/h3n2/ha/2y + +which creates three files in the current directory:: + + flu_seasonal_h3n2_ha_2y.json + flu_seasonal_h3n2_ha_2y_root-sequence.json + flu_seasonal_h3n2_ha_2y_tip-frequencies.json + +The --recursively option allows for downloading multiple datasets or narratives +at once, e.g. to download all the datasets under "ncov/open/…" into an existing +directory named "sars-cov-2":: + + nextstrain remote download --recursively nextstrain.org/ncov/open sars-cov-2/ + +which creates files for each dataset:: + + sars-cov-2/ncov_open_global.json + sars-cov-2/ncov_open_global_root-sequence.json + sars-cov-2/ncov_open_global_tip-frequencies.json + sars-cov-2/ncov_open_africa.json + sars-cov-2/ncov_open_africa_root-sequence.json + sars-cov-2/ncov_open_africa_tip-frequencies.json + … + +See :command-reference:`nextstrain remote` for more information on remote sources. + +positional arguments +==================== + + + +.. option:: + + Remote source URL for a dataset or narrative. A path prefix to scope/filter by if using :option:`--recursively`. + +.. option:: + + Local directory to save files in. May be a local filename to use if not using :option:`--recursively`. Defaults to current directory ("."). + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + +.. option:: --recursively, -r + + Download everything under the given remote URL path prefix + +.. option:: --dry-run + + Don't actually download anything, just show what would be downloaded + diff --git a/doc/commands/remote/index.rst b/doc/commands/remote/index.rst index 420c14c3..19aae9ee 100644 --- a/doc/commands/remote/index.rst +++ b/doc/commands/remote/index.rst @@ -1,18 +1,68 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain remote + +.. _nextstrain remote: + ================= nextstrain remote ================= -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: remote - :nosubcommands: +.. code-block:: none + + usage: nextstrain remote [-h] {upload,download,list,ls,delete,rm} ... + + +Upload, download, and manage remote datasets and narratives. + +nextstrain.org is the primary remote source/destination for most users, but +Amazon S3 buckets are also supported for some internal use cases. + +Remote sources/destinations are specified using URLs starting with +``https://nextstrain.org/`` and ``s3:///``. nextstrain.org remote +URLs represent datasets and narratives each as a whole, where datasets may +consist of multiple files (the main JSON file + optional sidecar files) when +uploading/downloading. Amazon S3 remote URLs represent dataset and narrative +files individually. + +For more details on using each remote, see their respective documentation +pages: + + * :doc:`/remotes/nextstrain.org` + * :doc:`/remotes/s3` + +For more information on dataset (Auspice JSON) and narrative (Markdown) files, +see :doc:`docs:reference/data-formats`. + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + +commands +======== + + + +.. option:: upload + + Upload dataset and narrative files. See :doc:`/commands/remote/upload`. + +.. option:: download + + Download dataset and narrative files. See :doc:`/commands/remote/download`. + +.. option:: list + + List datasets and narratives. See :doc:`/commands/remote/list`. + +.. option:: delete -Subcommands -=========== + Delete dataset and narratives. See :doc:`/commands/remote/delete`. - - :doc:`list` - - :doc:`upload` - - :doc:`download` - - :doc:`delete` diff --git a/doc/commands/remote/list.rst b/doc/commands/remote/list.rst index 7c73f13b..62721b05 100644 --- a/doc/commands/remote/list.rst +++ b/doc/commands/remote/list.rst @@ -1,9 +1,49 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain remote list + +.. _nextstrain remote list: + ====================== nextstrain remote list ====================== -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: remote list +.. code-block:: none + + usage: nextstrain remote list + nextstrain remote list --help + + +List datasets and narratives on a remote source. + +A remote source URL specifies what to list, e.g. to list what's in the +Nextstrain Group named "Blab":: + + nextstrain remote list nextstrain.org/groups/blab + +or list the core seasonal influenza datasets:: + + nextstrain remote list nextstrain.org/flu/seasonal + +See :command-reference:`nextstrain remote` for more information on remote sources. + +positional arguments +==================== + + + +.. option:: + + Remote source URL, with optional path prefix to scope/filter the results + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + diff --git a/doc/commands/remote/upload.rst b/doc/commands/remote/upload.rst index 2429b332..4da4595d 100644 --- a/doc/commands/remote/upload.rst +++ b/doc/commands/remote/upload.rst @@ -1,9 +1,63 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain remote upload + +.. _nextstrain remote upload: + ======================== nextstrain remote upload ======================== -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: remote upload +.. code-block:: none + + usage: nextstrain remote upload [ […]] + nextstrain remote upload --help + + +Upload dataset and narratives files to a remote destination. + +A remote destination URL specifies where to upload, e.g. to upload the dataset +files:: + + auspice/ncov_local.json + auspice/ncov_local_root-sequence.json + auspice/ncov_local_tip-frequencies.json + +so they're visible at `https://nextstrain.org/groups/example/ncov`:: + + nextstrain remote upload nextstrain.org/groups/example/ncov auspice/ncov_local*.json + +If uploading multiple datasets or narratives, uploading to the top-level of a +Nextstrain Group, or uploading to an S3 remote, then the local filenames are +used in combination with any path prefix in the remote source URL. + +See :command-reference:`nextstrain remote` for more information on remote sources. + +positional arguments +==================== + + + +.. option:: + + Remote destination URL for a dataset or narrative. A path prefix if the files to upload comprise more than one dataset or narrative or the remote is S3. + +.. option:: [ […]] + + Files to upload. Typically dataset and sidecar files (Auspice JSON files) and/or narrative files (Markdown files). + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + +.. option:: --dry-run + + Don't actually upload anything, just show what would be uploaded + diff --git a/doc/commands/setup.rst b/doc/commands/setup.rst index 5104e766..79337443 100644 --- a/doc/commands/setup.rst +++ b/doc/commands/setup.rst @@ -1,9 +1,56 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain setup + +.. _nextstrain setup: + ================ nextstrain setup ================ -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: setup +.. code-block:: none + + usage: nextstrain setup [-h] [--dry-run] [--force] [--set-default] + + +Sets up a Nextstrain runtime for use with `nextstrain build`, `nextstrain +view`, etc. + +Only the Conda runtime currently supports automated set up, but this command +may still be used with other runtimes to check an existing (manual) setup and +set the runtime as the default on success. + +Exits with an error code if automated set up fails or if setup checks fail. + +positional arguments +==================== + + + +.. option:: + + The Nextstrain runtime to set up. One of {docker, conda, singularity, ambient, aws-batch}. + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + +.. option:: --dry-run + + Don't actually set up anything, just show what would happen. + +.. option:: --force + + Ignore existing setup, if any, and always start fresh. + +.. option:: --set-default + + Use the runtime as the default if set up is successful. + diff --git a/doc/commands/shell.rst b/doc/commands/shell.rst index a52d6b59..612a9c6d 100644 --- a/doc/commands/shell.rst +++ b/doc/commands/shell.rst @@ -1,9 +1,116 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain shell + +.. _nextstrain shell: + ================ nextstrain shell ================ -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: shell +.. code-block:: none + + usage: nextstrain shell [options] [...] + nextstrain shell --help + + +Start a new shell inside a Nextstrain runtime to run ad-hoc +commands and perform debugging. + +positional arguments +==================== + + + +.. option:: + + Path to pathogen build directory + +.. option:: ... + + Additional arguments to pass to the executed program + +options +======= + + + +.. option:: --help, -h + + Show a brief help message of common options and exit + +.. option:: --help-all + + Show a full help message of all options and exit + +runtime selection options +========================= + +Select the Nextstrain runtime to use, if the +default is not suitable. + +.. option:: --docker + + Run commands inside a container image using Docker. (default) + +.. option:: --conda + + Run commands with access to a fully-managed Conda environment. + +.. option:: --singularity + + Run commands inside a container image using Singularity. + +runtime options +=============== + +Options shared by all runtimes. + +.. option:: --env [=] + + Set the environment variable ```` to the value in the current environment (i.e. pass it thru) or to the given ````. May be specified more than once. Overrides any variables of the same name set via :option:`--envdir`. When this option or :option:`--envdir` is given, the default behaviour of automatically passing thru several "well-known" variables is disabled. The "well-known" variables are ``AUGUR_RECURSION_LIMIT``, ``AUGUR_MINIFY_JSON``, ``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``, ``AWS_SESSION_TOKEN``, ``ID3C_URL``, ``ID3C_USERNAME``, ``ID3C_PASSWORD``, ``RETHINK_HOST``, and ``RETHINK_AUTH_KEY``. Pass those variables explicitly via :option:`--env` or :option:`--envdir` if you need them in combination with other variables. + +.. option:: --envdir + + Set environment variables from the envdir at ````. May be specified more than once. An envdir is a directory containing files describing environment variables. Each filename is used as the variable name. The first line of the contents of each file is used as the variable value. When this option or :option:`--env` is given, the default behaviour of automatically passing thru several "well-known" variables is disabled. See the description of :option:`--env` for more details. + +development options +=================== + +These should generally be unnecessary unless you're developing Nextstrain. + +.. option:: --image + + Container image name to use for the Nextstrain runtime (default: nextstrain/base for Docker and AWS Batch, docker://nextstrain/base for Singularity) + +.. option:: --exec + + Program to run inside the runtime + +development options for --docker +================================ + + + +.. option:: --augur + + Replace the image's copy of augur with a local copy + +.. option:: --auspice + + Replace the image's copy of auspice with a local copy + +.. option:: --fauna + + Replace the image's copy of fauna with a local copy + +.. option:: --sacra + + Replace the image's copy of sacra with a local copy + +.. option:: --docker-arg ... + + Additional arguments to pass to `docker run` + diff --git a/doc/commands/update.rst b/doc/commands/update.rst index 6ea978db..0a06a50a 100644 --- a/doc/commands/update.rst +++ b/doc/commands/update.rst @@ -1,9 +1,48 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain update + +.. _nextstrain update: + ================= nextstrain update ================= -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: update +.. code-block:: none + + usage: nextstrain update [-h] [] + + +Updates a Nextstrain runtime to the latest available version, if any. + +The default runtime (docker) is updated when this command is run +without arguments. Provide a runtime name as an argument to update a specific +runtime instead. + +Three runtimes currently support updates: Docker, Conda, and Singularity. +Updates may take several minutes as new software versions are downloaded. + +This command also checks for newer versions of the Nextstrain CLI (the +`nextstrain` program) itself and will suggest upgrade instructions if an +upgrade is available. + +positional arguments +==================== + + + +.. option:: + + The Nextstrain runtime to check. One of {docker, conda, singularity, ambient, aws-batch}. (default: docker) + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + diff --git a/doc/commands/version.rst b/doc/commands/version.rst index da22556e..99576512 100644 --- a/doc/commands/version.rst +++ b/doc/commands/version.rst @@ -1,9 +1,32 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain version + +.. _nextstrain version: + ================== nextstrain version ================== -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: version +.. code-block:: none + + usage: nextstrain version [-h] [--verbose] + + +Prints the version of the Nextstrain CLI. + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + +.. option:: --verbose + + Show versions of individual Nextstrain components in each runtime + diff --git a/doc/commands/view.rst b/doc/commands/view.rst index e4293366..c91508ad 100644 --- a/doc/commands/view.rst +++ b/doc/commands/view.rst @@ -1,9 +1,178 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain view + +.. _nextstrain view: + =============== nextstrain view =============== -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: view +.. code-block:: none + + usage: nextstrain view [options] + nextstrain view --help + + +Visualizes a completed pathogen builds or narratives in Auspice, the Nextstrain +visualization app. + +:option:`` may be a `dataset (.json) file`_ or `narrative (.md) file`_ to start +Auspice and directly open the specified dataset or narrative in a browser. +Adjacent datasets and/or narratives may also be viewable as an appropriate data +directory for Auspice is automatically inferred from the file path. + +:option:`` may also be a directory with one of the following layouts:: + + / + ├── auspice/ + │ └── *.json + └── narratives/ + └── *.md + + / + ├── auspice/ + │ └── *.json + └── *.md + + / + ├── *.json + └── narratives/ + └── *.md + + / + ├── *.json + └── *.md + +Dataset and narrative files will be served, respectively, from **auspice** +and/or **narratives** subdirectories under the given :option:`` if the +subdirectories exist. Otherwise, files will be served from the given directory +:option:`` itself. + +If your pathogen build directory follows our conventional layout by containing +an **auspice** directory (and optionally a **narratives** directory), then you +can give `nextstrain view` the same path as you do `nextstrain build`. + +Note that by convention files named **README.md** or **group-overview.md** will +be ignored for the purposes of finding available narratives. + +.. _dataset (.json) file: https://docs.nextstrain.org/page/reference/glossary.html#term-dataset +.. _narrative (.md) file: https://docs.nextstrain.org/page/reference/glossary.html#term-narrative + +positional arguments +==================== + + + +.. option:: + + Path to a directory containing dataset JSON and/or narrative Markdown files for Auspice, or a directory containing an auspice/ and/or narratives/ directory, or a specific dataset JSON or narrative Markdown file. + +options +======= + + + +.. option:: --help, -h + + Show a brief help message of common options and exit + +.. option:: --help-all + + Show a full help message of all options and exit + +.. option:: --open + + Open a web browser automatically (the default) + +.. option:: --no-open + + Do not open a web browser automatically + +.. option:: --allow-remote-access + + Allow other computers on the network to access the website (alias for --host=0.0.0.0) + +.. option:: --host + + Listen on the given hostname or IP address instead of the default 127.0.0.1 + +.. option:: --port + + Listen on the given port instead of the default port 4000 + +runtime selection options +========================= + +Select the Nextstrain runtime to use, if the +default is not suitable. + +.. option:: --docker + + Run commands inside a container image using Docker. (default) + +.. option:: --ambient + + Run commands in the ambient environment, outside of any container image. + +.. option:: --conda + + Run commands with access to a fully-managed Conda environment. + +.. option:: --singularity + + Run commands inside a container image using Singularity. + +runtime options +=============== + +Options shared by all runtimes. + +.. option:: --env [=] + + Set the environment variable ```` to the value in the current environment (i.e. pass it thru) or to the given ````. May be specified more than once. Overrides any variables of the same name set via :option:`--envdir`. When this option or :option:`--envdir` is given, the default behaviour of automatically passing thru several "well-known" variables is disabled. The "well-known" variables are ``AUGUR_RECURSION_LIMIT``, ``AUGUR_MINIFY_JSON``, ``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``, ``AWS_SESSION_TOKEN``, ``ID3C_URL``, ``ID3C_USERNAME``, ``ID3C_PASSWORD``, ``RETHINK_HOST``, and ``RETHINK_AUTH_KEY``. Pass those variables explicitly via :option:`--env` or :option:`--envdir` if you need them in combination with other variables. + +.. option:: --envdir + + Set environment variables from the envdir at ````. May be specified more than once. An envdir is a directory containing files describing environment variables. Each filename is used as the variable name. The first line of the contents of each file is used as the variable value. When this option or :option:`--env` is given, the default behaviour of automatically passing thru several "well-known" variables is disabled. See the description of :option:`--env` for more details. + +development options +=================== + +These should generally be unnecessary unless you're developing Nextstrain. + +.. option:: --image + + Container image name to use for the Nextstrain runtime (default: nextstrain/base for Docker and AWS Batch, docker://nextstrain/base for Singularity) + +.. option:: --exec + + Program to run inside the runtime + +development options for --docker +================================ + + + +.. option:: --augur + + Replace the image's copy of augur with a local copy + +.. option:: --auspice + + Replace the image's copy of auspice with a local copy + +.. option:: --fauna + + Replace the image's copy of fauna with a local copy + +.. option:: --sacra + + Replace the image's copy of sacra with a local copy + +.. option:: --docker-arg ... + + Additional arguments to pass to `docker run` + diff --git a/doc/commands/whoami.rst b/doc/commands/whoami.rst index 58f9762a..2886e852 100644 --- a/doc/commands/whoami.rst +++ b/doc/commands/whoami.rst @@ -1,9 +1,33 @@ +.. default-role:: literal + +.. role:: command-reference(ref) + +.. program:: nextstrain whoami + +.. _nextstrain whoami: + ================= nextstrain whoami ================= -.. argparse:: - :module: nextstrain.cli - :func: make_parser - :prog: nextstrain - :path: whoami +.. code-block:: none + + usage: nextstrain whoami [-h] + + +Show information about the logged-in user. + +The username, email address, and Nextstrain Groups memberships of the currently +logged-in user are shown. + +Exits with an error if no one is logged in. + +options +======= + + + +.. option:: -h, --help + + show this help message and exit + diff --git a/doc/conf.py b/doc/conf.py index aa2412e0..c519ba50 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -37,7 +37,6 @@ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx_markdown_tables', - 'sphinxarg.ext', 'nextstrain.sphinx.theme', ] diff --git a/nextstrain/cli/__init__.py b/nextstrain/cli/__init__.py index 89dca81c..5eb6c64c 100644 --- a/nextstrain/cli/__init__.py +++ b/nextstrain/cli/__init__.py @@ -6,7 +6,7 @@ and Auspice across computing platforms such as Docker, Conda, and AWS Batch. Run `nextstrain --help` for usage information about each command. -See <:doc:`/`> for more documentation. +See <:doc:`/index`> for more documentation. """ diff --git a/nextstrain/cli/argparse.py b/nextstrain/cli/argparse.py index 5faa3828..474e223b 100644 --- a/nextstrain/cli/argparse.py +++ b/nextstrain/cli/argparse.py @@ -1,17 +1,25 @@ """ Custom helpers for extending the behaviour of argparse standard library. """ +import os import sys -from argparse import Action, ArgumentDefaultsHelpFormatter, ArgumentTypeError, SUPPRESS -from itertools import takewhile +from argparse import Action, ArgumentDefaultsHelpFormatter, ArgumentParser, ArgumentTypeError, SUPPRESS, _SubParsersAction # pyright: ignore[reportPrivateUsage] +from itertools import chain, takewhile from pathlib import Path from textwrap import indent from types import SimpleNamespace +from typing import Iterable, Optional, Tuple from .rst import rst_to_text from .types import RunnerModule from .util import format_usage, runner_module +# The standard argument group title for non-positional arguments. See +# and +# . +OPTIONS_TITLE = "options" if sys.version_info >= (3, 10) else "optional arguments" + + # Include this in an argument help string to suppress the automatic appending # of the default value by argparse.ArgumentDefaultsHelpFormatter. This works # because the automatic appending is conditional on the presence of %(default), @@ -24,9 +32,30 @@ class HelpFormatter(ArgumentDefaultsHelpFormatter): + def __init__(self, prog, indent_increment = 2, max_help_position = 24, width = None): + # Ignore terminal size, unlike standard argparse, as the readability of + # paragraphs of text suffers at wide widths. Instead, default to 78 + # columns (80 wide - 2 column gutter), but let that be overridden by an + # explicit COLUMNS variable or reduced by a smaller actual terminal. + if width is None: + try: + width = int(os.environ["COLUMNS"]) + except (KeyError, ValueError): + try: + width = min(os.get_terminal_size().columns, 80) - 2 + except (AttributeError, OSError): + width = 80 - 2 + + super().__init__(prog, indent_increment, max_help_position, width) + # Based on argparse.RawDescriptionHelpFormatter's implementation def _fill_text(self, text, width, prefix): - return indent(rst_to_text(text), prefix) + return indent(rst_to_text(text, width), prefix) + + # Based on argparse.RawTextHelpFormatter's implementation + def _split_lines(self, text, width): + # Render to rST here so rST gets control over wrapping/line breaks. + return rst_to_text(text, width).splitlines() def register_default_command(parser): @@ -118,10 +147,6 @@ def truncate_help(self, full_help): Truncate the full help after the standard "options" (or "optional arguments") listing and before any custom argument groups. """ - # See - # and . - heading = "options:\n" if sys.version_info >= (3, 10) else "optional arguments:\n" - seen_optional_arguments_heading = False def before_extra_argument_groups(line): @@ -133,7 +158,7 @@ def before_extra_argument_groups(line): nonlocal seen_optional_arguments_heading if not seen_optional_arguments_heading: - if line == heading: + if line == f"{OPTIONS_TITLE}:\n": seen_optional_arguments_heading = True return not seen_optional_arguments_heading \ @@ -211,3 +236,25 @@ def DirectoryPath(value: str) -> Path: raise ArgumentTypeError(f"not a directory: {value}") return path + + +def walk_commands(parser: ArgumentParser, command: Optional[Tuple[str, ...]] = None) -> Iterable[Tuple[Tuple[str, ...], ArgumentParser]]: + if command is None: + command = (parser.prog,) + + yield command, parser + + subparsers = chain.from_iterable( + action.choices.items() + for action in parser._actions + if isinstance(action, _SubParsersAction)) + + visited = set() + + for subname, subparser in subparsers: + if subparser in visited: + continue + + visited.add(subparser) + + yield from walk_commands(subparser, (*command, subname)) diff --git a/nextstrain/cli/command/authorization.py b/nextstrain/cli/command/authorization.py index 24ec502f..f7fce0e6 100644 --- a/nextstrain/cli/command/authorization.py +++ b/nextstrain/cli/command/authorization.py @@ -2,7 +2,7 @@ Produce an Authorization header appropriate for nextstrain.org's web API. This is a development tool unnecessary for normal usage. It's useful for -directly making API requests to nextstrain.org with ``curl`` or similar +directly making API requests to nextstrain.org with `curl` or similar commands. For example:: curl -si https://nextstrain.org/whoami \\ diff --git a/nextstrain/cli/command/build.py b/nextstrain/cli/command/build.py index 00680206..57483dfc 100644 --- a/nextstrain/cli/command/build.py +++ b/nextstrain/cli/command/build.py @@ -6,7 +6,7 @@ You need at least one runtime available to run a build. You can test if the Docker, Conda, ambient, or AWS Batch runtimes are properly supported on your -computer by running: +computer by running:: nextstrain check-setup @@ -38,14 +38,14 @@ def register_parser(subparser): parser.add_argument( "--detach", help = "Run the build in the background, detached from your terminal. " - "Re-attach later using --attach. " - "Currently only supported when also using --aws-batch.", + "Re-attach later using :option:`--attach`. " + "Currently only supported when also using :option:`--aws-batch`.", action = "store_true") parser.add_argument( "--attach", - help = "Re-attach to a --detach'ed build to view output and download results. " - "Currently only supported when also using --aws-batch.", + help = "Re-attach to a :option:`--detach`'ed build to view output and download results. " + "Currently only supported when also using :option:`--aws-batch`.", metavar = "") parser.add_argument( @@ -73,17 +73,18 @@ def register_parser(subparser): "--download", metavar = "", help = dedent(f"""\ - Only download new or modified files matching from the + Only download new or modified files matching ```` from the remote build. Shell-style advanced globbing is supported, but be sure to escape wildcards or quote the whole pattern so your shell doesn't expand them. May be passed more than once. Currently only - supported when also using --aws-batch. Default is to download - every new or modified file. + supported when also using :option:`--aws-batch`. Default is to + download every new or modified file. - Besides basic glob features like single-part wildcards (*), - character classes ([…]), and brace expansion ({{…, …}}), several - advanced globbing features are also supported: multi-part wildcards - (**), extended globbing (@(…), +(…), etc.), and negation (!…). + Besides basic glob features like single-part wildcards (``*``), + character classes (``[…]``), and brace expansion (``{{…, …}}``), + several advanced globbing features are also supported: multi-part + wildcards (``**``), extended globbing (``@(…)``, ``+(…)``, etc.), + and negation (``!…``). {SKIP_AUTO_DEFAULT_IN_HELP} """), @@ -93,7 +94,7 @@ def register_parser(subparser): parser.add_argument( "--no-download", help = "Do not download any files from the remote build when it completes. " - "Currently only supported when also using --aws-batch." + "Currently only supported when also using :option:`--aws-batch`." f"{SKIP_AUTO_DEFAULT_IN_HELP}", dest = "download", action = "store_false") @@ -108,7 +109,7 @@ def register_parser(subparser): parser.add_argument( "--no-logs", help = "Do not show the log messages of the remote build. " - "Currently only supported when also using --aws-batch. " + "Currently only supported when also using :option:`--aws-batch`. " "Default is to show all log messages, even when attaching to a completed build." f"{SKIP_AUTO_DEFAULT_IN_HELP}", dest = "logs", diff --git a/nextstrain/cli/command/check_setup.py b/nextstrain/cli/command/check_setup.py index 4a0e20fd..3b9b4d00 100644 --- a/nextstrain/cli/command/check_setup.py +++ b/nextstrain/cli/command/check_setup.py @@ -46,6 +46,10 @@ def register_parser(subparser): + """ + %(prog)s [--set-default] [ [ ...]] + %(prog)s --help + """ parser = subparser.add_parser("check-setup", help = "Check runtime setups") parser.add_argument( diff --git a/nextstrain/cli/command/login.py b/nextstrain/cli/command/login.py index c04ee3b0..25f58e45 100644 --- a/nextstrain/cli/command/login.py +++ b/nextstrain/cli/command/login.py @@ -13,12 +13,10 @@ command sooner than usual. Your password itself is never saved locally. - -For automation purposes, you may opt to provide the username and password to -use in the environment variables NEXTSTRAIN_USERNAME and NEXTSTRAIN_PASSWORD. """ from functools import partial from getpass import getpass +from inspect import cleandoc from os import environ from ..authn import current_user, login, renew from ..errors import UserError @@ -33,7 +31,7 @@ def register_parser(subparser): parser.add_argument( "--username", "-u", metavar = "", - help = "The username to log in as. If not provided, the NEXTSTRAIN_USERNAME" + help = "The username to log in as. If not provided, the :envvar:`NEXTSTRAIN_USERNAME`" " environment variable will be used if available, otherwise you'll be" " prompted to enter your username.", default = environ.get("NEXTSTRAIN_USERNAME")) @@ -53,6 +51,21 @@ def register_parser(subparser): " than the tokens would normally be renewed.", action = "store_true") + parser.epilog = cleandoc(""" + For automation purposes, you may opt to provide environment variables instead + of interactive input and/or command-line options: + + .. envvar:: NEXTSTRAIN_USERNAME + + Username on nextstrain.org. Ignored if :option:`--username` is also + provided. + + .. envvar:: NEXTSTRAIN_PASSWORD + + Password for nextstrain.org user. Required if :option:`--no-prompt` is + used without existing valid/renewable tokens. + """) + return parser diff --git a/nextstrain/cli/command/remote/__init__.py b/nextstrain/cli/command/remote/__init__.py index 2f2d80b2..167f24dd 100644 --- a/nextstrain/cli/command/remote/__init__.py +++ b/nextstrain/cli/command/remote/__init__.py @@ -1,11 +1,11 @@ """ Upload, download, and manage remote datasets and narratives. -nextstrain.org is the primary remote source/destination, but Amazon S3 buckets -are also supported and necessary for some use cases. [#history]_ +nextstrain.org is the primary remote source/destination for most users, but +Amazon S3 buckets are also supported for some internal use cases. Remote sources/destinations are specified using URLs starting with -`https://nextstrain.org/` and `s3:///`. nextstrain.org remote +``https://nextstrain.org/`` and ``s3:///``. nextstrain.org remote URLs represent datasets and narratives each as a whole, where datasets may consist of multiple files (the main JSON file + optional sidecar files) when uploading/downloading. Amazon S3 remote URLs represent dataset and narrative @@ -19,12 +19,6 @@ For more information on dataset (Auspice JSON) and narrative (Markdown) files, see :doc:`docs:reference/data-formats`. - -.. [#history] In previous versions, only Amazon S3 buckets were supported. The - introduction of nextstrain.org support largely obsoletes the need to use S3 - directly. Exceptions include if you need to manage v1 datasets (i.e. separate - ``*_tree.json`` and ``*_meta.json`` files) or Nextstrain Group overview/logo - files (``group-overview.md`` or ``group-logo.png``). """ # Guard against __doc__ being None to appease the type checkers. diff --git a/nextstrain/cli/command/remote/delete.py b/nextstrain/cli/command/remote/delete.py index 7067ec21..491641b2 100644 --- a/nextstrain/cli/command/remote/delete.py +++ b/nextstrain/cli/command/remote/delete.py @@ -6,12 +6,12 @@ nextstrain remote delete nextstrain.org/groups/blab/beta-cov -The --recursively option allows for deleting multiple datasets or narratives +The :option:`--recursively` option allows for deleting multiple datasets or narratives at once, e.g. to delete all the "ncov/wa/…" datasets in the "blab" group:: nextstrain remote delete --recursively nextstrain.org/groups/blab/ncov/wa -See `nextstrain remote --help` for more information on remote sources. +See :command-reference:`nextstrain remote` for more information on remote sources. """ from ... import console @@ -32,7 +32,7 @@ def register_parser(subparser): parser.add_argument( "remote_path", help = "Remote source URL for a dataset or narrative. " - "A path prefix to scope/filter by if using --recursively.", + "A path prefix to scope/filter by if using :option:`--recursively`.", metavar = "") parser.add_argument( diff --git a/nextstrain/cli/command/remote/download.py b/nextstrain/cli/command/remote/download.py index 0886d50f..d4e55f63 100644 --- a/nextstrain/cli/command/remote/download.py +++ b/nextstrain/cli/command/remote/download.py @@ -28,7 +28,7 @@ sars-cov-2/ncov_open_africa_tip-frequencies.json … -See `nextstrain remote --help` for more information on remote sources. +See :command-reference:`nextstrain remote` for more information on remote sources. """ import shlex @@ -49,13 +49,13 @@ def register_parser(subparser): parser.add_argument( "remote_path", help = "Remote source URL for a dataset or narrative. " - "A path prefix to scope/filter by if using --recursively.", + "A path prefix to scope/filter by if using :option:`--recursively`.", metavar = "") parser.add_argument( "local_path", help = "Local directory to save files in. " - "May be a local filename to use if not using --recursively. " + "May be a local filename to use if not using :option:`--recursively`. " 'Defaults to current directory ("%(default)s").', metavar = "", type = Path, diff --git a/nextstrain/cli/command/remote/ls.py b/nextstrain/cli/command/remote/ls.py index f1c7a5f0..457fa30f 100644 --- a/nextstrain/cli/command/remote/ls.py +++ b/nextstrain/cli/command/remote/ls.py @@ -10,7 +10,7 @@ nextstrain remote list nextstrain.org/flu/seasonal -See `nextstrain remote --help` for more information on remote sources. +See :command-reference:`nextstrain remote` for more information on remote sources. """ from ...remote import parse_remote_path diff --git a/nextstrain/cli/command/remote/upload.py b/nextstrain/cli/command/remote/upload.py index db19a6a6..84b658a4 100644 --- a/nextstrain/cli/command/remote/upload.py +++ b/nextstrain/cli/command/remote/upload.py @@ -16,7 +16,7 @@ Nextstrain Group, or uploading to an S3 remote, then the local filenames are used in combination with any path prefix in the remote source URL. -See `nextstrain remote --help` for more information on remote sources. +See :command-reference:`nextstrain remote` for more information on remote sources. """ from pathlib import Path diff --git a/nextstrain/cli/command/update.py b/nextstrain/cli/command/update.py index 59a85e46..ff79c654 100644 --- a/nextstrain/cli/command/update.py +++ b/nextstrain/cli/command/update.py @@ -9,7 +9,7 @@ Updates may take several minutes as new software versions are downloaded. This command also checks for newer versions of the Nextstrain CLI (the -``nextstrain`` program) itself and will suggest upgrade instructions if an +`nextstrain` program) itself and will suggest upgrade instructions if an upgrade is available. """ from functools import partial diff --git a/nextstrain/cli/command/view.py b/nextstrain/cli/command/view.py index c58110e4..f86c33ad 100644 --- a/nextstrain/cli/command/view.py +++ b/nextstrain/cli/command/view.py @@ -2,12 +2,12 @@ Visualizes a completed pathogen builds or narratives in Auspice, the Nextstrain visualization app. - may be a `dataset (.json) file`_ or `narrative (.md) file`_ to start +:option:`` may be a `dataset (.json) file`_ or `narrative (.md) file`_ to start Auspice and directly open the specified dataset or narrative in a browser. Adjacent datasets and/or narratives may also be viewable as an appropriate data directory for Auspice is automatically inferred from the file path. - may also be a directory with one of the following layouts:: +:option:`` may also be a directory with one of the following layouts:: / ├── auspice/ @@ -30,13 +30,13 @@ └── *.md Dataset and narrative files will be served, respectively, from **auspice** -and/or **narratives** subdirectories under the given if the +and/or **narratives** subdirectories under the given :option:`` if the subdirectories exist. Otherwise, files will be served from the given directory - itself. +:option:`` itself. If your pathogen build directory follows our conventional layout by containing an **auspice** directory (and optionally a **narratives** directory), then you -can give ``nextstrain view`` the same path as you do ``nextstrain build``. +can give `nextstrain view` the same path as you do `nextstrain build`. Note that by convention files named **README.md** or **group-overview.md** will be ignored for the purposes of finding available narratives. diff --git a/nextstrain/cli/remote/nextstrain_dot_org.py b/nextstrain/cli/remote/nextstrain_dot_org.py index 4c7f5c8c..52d01fe8 100644 --- a/nextstrain/cli/remote/nextstrain_dot_org.py +++ b/nextstrain/cli/remote/nextstrain_dot_org.py @@ -40,7 +40,7 @@ ===================== .. warning:: - For development only. You don't need to set this during normal operation. + For development only. You don't need to set these during normal operation. .. envvar:: NEXTSTRAIN_DOT_ORG diff --git a/nextstrain/cli/rst/__init__.py b/nextstrain/cli/rst/__init__.py index cba14dfd..0d9361e8 100644 --- a/nextstrain/cli/rst/__init__.py +++ b/nextstrain/cli/rst/__init__.py @@ -19,9 +19,12 @@ import os import re from docutils.core import publish_string as convert_rst_to_string, publish_doctree as convert_rst_to_doctree # type: ignore +from docutils.parsers.rst import Directive +from docutils.parsers.rst.directives import register_directive from docutils.parsers.rst.roles import register_local_role from docutils.utils import unescape # type: ignore from ..__version__ import __version__ as cli_version +from ..util import remove_suffix from .sphinx import TextWriter @@ -37,10 +40,16 @@ REPORT_LEVEL_NONE = 5 REPORT_LEVEL = REPORT_LEVEL_WARNINGS if STRICT else REPORT_LEVEL_NONE -ROLES_REGISTERED = False +REGISTERED = False +# Some of these custom roles are identified by name and specially-processed in +# our sphinx.TextWriter, e.g. see visit_literal() and visit_Text(). PREAMBLE = """ -.. default-role:: literal +.. role:: command-invocation(literal) +.. role:: command-reference(literal) +.. role:: option(literal) +.. role:: envvar(literal) +.. default-role:: command-invocation """ POSTAMBLE = """ @@ -48,7 +57,7 @@ """ -def rst_to_text(source: str) -> str: +def rst_to_text(source: str, width: int = None) -> str: """ Converts rST *source* to plain text. @@ -62,16 +71,18 @@ def rst_to_text(source: str) -> str: Sphinx directives, roles, etc. are not supported. - The default role is ``literal`` instead of ``title-reference``. + The default role is ``command-invocation`` (a ``literal`` subrole which + emits the text surrounded by backticks) instead of ``title-reference``. If conversion fails, *source* is returned. Set :envvar:`NEXTSTRAIN_RST_STRICT` to enable strict conversion and raise exceptions for failures. """ - global ROLES_REGISTERED - if not ROLES_REGISTERED: + global REGISTERED + if not REGISTERED: + register_directive("envvar", EnvVar) register_local_role("doc", doc_reference_role) - ROLES_REGISTERED = True + REGISTERED = True settings = { # Use Unicode strings for I/O, not encoded bytes. @@ -88,7 +99,7 @@ def rst_to_text(source: str) -> str: return convert_rst( "\n".join([PREAMBLE, source, POSTAMBLE]), reader = Reader(), - writer = TextWriter(), + writer = TextWriter(width = width), settings_overrides = settings, enable_exit_status = STRICT) except: @@ -116,6 +127,32 @@ def convert_rst(source: str, *, reader, writer, settings_overrides: dict, enable enable_exit_status = enable_exit_status) +# See docutils.parsers.rst.Directive and docutils.parsers.rst.directives and +# submodules for this API and examples. +class EnvVar(Directive): + required_arguments = 1 + has_content = True + + def run(self): + # XXX TODO: Inspect self.state.parent (or similar) and add more items + # to that if we're adjacent to a previous envvar directive's list? Or + # maybe it's better to use a docutils Transform to combine them + # afterwards? But either way, it doesn't really matter for our + # sphinx.TextWriter, so punting on that. + # -trs, 20 July 2023 + dl = docutils.nodes.definition_list(rawtext = self.block_text) + li = docutils.nodes.definition_list_item() + dt = docutils.nodes.term(text = self.arguments[0]) + dd = docutils.nodes.definition(rawtext = "\n".join(self.content)) + + dl += li + li += [dt, dd] + + self.state.nested_parse(self.content, self.content_offset, dd) + + return [dl] + + # See docutils.parsers.rst.roles for this API and examples. def doc_reference_role(role, rawtext, text, lineno, inliner, options={}, content=[]): """ @@ -184,7 +221,13 @@ def doc_url(target: str) -> str: # Oh well. return target - return project_url.rstrip("/") + "/" + path.lstrip("/") + suffix + if path.endswith("/index"): + # a/b/index → a/b/ (e.g. implicitly a/b/index.html) + path_url = remove_suffix("index", path) + else: + path_url = path + (suffix or "") + + return project_url.rstrip("/") + "/" + path_url.lstrip("/") class Reader(docutils.readers.standalone.Reader): diff --git a/nextstrain/cli/rst/sphinx.py b/nextstrain/cli/rst/sphinx.py index ef038c90..0ca24785 100644 --- a/nextstrain/cli/rst/sphinx.py +++ b/nextstrain/cli/rst/sphinx.py @@ -19,6 +19,10 @@ from docutils.utils import column_width +MAXWIDTH = 80 +STDINDENT = 4 + + # Stubs taking the place of a full Sphinx builder with config and what not. # The builder in Sphinx is a global context object ("god object") which isn't # very amenable to extraction. @@ -26,6 +30,7 @@ class TextConfig: text_sectionchars = '*=-~"+`' text_add_secnumbers = False # Sphinx default is True, but we don't want 'em. text_secnumber_suffix = ". " # referenced but not used because above is False. + text_max_width = MAXWIDTH class TextBuilder: config = TextConfig() @@ -413,10 +418,6 @@ def _handle_long_word(self, reversed_chunks: List[str], cur_line: List[str], cur_line.append(reversed_chunks.pop()) -MAXWIDTH = 80 -STDINDENT = 4 - - def my_wrap(text: str, width: int = MAXWIDTH, **kwargs: Any) -> List[str]: w = TextWrapper(width=width, **kwargs) return w.wrap(text) @@ -429,9 +430,11 @@ class TextWriter(writers.Writer): output: str = None - def __init__(self, builder: "TextBuilder" = None) -> None: + def __init__(self, builder: "TextBuilder" = None, width: int = None) -> None: super().__init__() self.builder = builder or TextBuilder() + if width: + self.builder.config.text_max_width = width def translate(self) -> None: visitor = TextTranslator(self.document, self.builder) @@ -449,6 +452,7 @@ def __init__(self, document: nodes.document, builder: "TextBuilder") -> None: self.sectionchars = self.config.text_sectionchars self.add_secnumbers = self.config.text_add_secnumbers self.secnumber_suffix = self.config.text_secnumber_suffix + self.max_width = self.config.text_max_width self.states: List[List[Tuple[int, Union[str, List[str]]]]] = [[]] self.stateindent = [0] self.list_counter: List[int] = [] @@ -474,7 +478,7 @@ def do_format() -> None: if not toformat: return if wrap: - res = my_wrap(''.join(toformat), width=MAXWIDTH - maxindent) + res = my_wrap(''.join(toformat), width=self.max_width - maxindent) else: res = ''.join(toformat).splitlines() if end: @@ -843,7 +847,7 @@ def visit_image(self, node: Element) -> None: def visit_transition(self, node: Element) -> None: indent = sum(self.stateindent) self.new_state(0) - self.add_text('=' * (MAXWIDTH - indent)) + self.add_text('=' * (self.max_width - indent)) self.end_state() raise nodes.SkipNode @@ -887,9 +891,10 @@ def depart_list_item(self, node: Element) -> None: def visit_definition_list_item(self, node: Element) -> None: self._classifier_count_in_li = len(list(node.traverse(nodes.classifier))) + self.new_state(2) def depart_definition_list_item(self, node: Element) -> None: - pass + self.end_state() def visit_term(self, node: Element) -> None: self.new_state(0) @@ -981,7 +986,7 @@ def _depart_admonition(self, node: Element) -> None: indent = sum(self.stateindent) + len(label) if (len(self.states[-1]) == 1 and self.states[-1][0][0] == 0 and - MAXWIDTH - indent >= sum(len(s) for s in self.states[-1][0][1])): + self.max_width - indent >= sum(len(s) for s in self.states[-1][0][1])): # short text: append text after admonition label self.stateindent[-1] += len(label) self.end_state(first=label + ': ') @@ -1153,10 +1158,16 @@ def depart_title_reference(self, node: Element) -> None: self.add_text('*') def visit_literal(self, node: Element) -> None: - self.add_text('`') + if 'command-invocation' in node['classes']: + self.add_text('`') + elif 'command-reference' in node['classes']: + self.add_text('`') def depart_literal(self, node: Element) -> None: - self.add_text('`') + if 'command-invocation' in node['classes']: + self.add_text('`') + elif 'command-reference' in node['classes']: + self.add_text(' --help`') def visit_subscript(self, node: Element) -> None: self.add_text('_') diff --git a/nextstrain/cli/runner/__init__.py b/nextstrain/cli/runner/__init__.py index 0013ff91..f9fffcc2 100644 --- a/nextstrain/cli/runner/__init__.py +++ b/nextstrain/cli/runner/__init__.py @@ -153,12 +153,12 @@ def register_arguments(parser: ArgumentParser, runners: List[RunnerModule], exec runtime.add_argument( "--env", metavar = "[=]", - help = "Set the environment variable to the value in the current environment (i.e. pass it thru) or to the given . " + help = "Set the environment variable ```` to the value in the current environment (i.e. pass it thru) or to the given ````. " "May be specified more than once. " - "Overrides any variables of the same name set via --envdir. " - "When this option or --envdir is given, the default behaviour of automatically passing thru several \"well-known\" variables is disabled. " - f"The \"well-known\" variables are {prose_list(hostenv.forwarded_names, 'and')}. " - "Pass those variables explicitly via --env or --envdir if you need them in combination with other variables. " + "Overrides any variables of the same name set via :option:`--envdir`. " + "When this option or :option:`--envdir` is given, the default behaviour of automatically passing thru several \"well-known\" variables is disabled. " + f"The \"well-known\" variables are {prose_list([f'``{x}``' for x in hostenv.forwarded_names], 'and')}. " + "Pass those variables explicitly via :option:`--env` or :option:`--envdir` if you need them in combination with other variables. " f"{SKIP_AUTO_DEFAULT_IN_HELP}", action = "append", default = []) @@ -166,13 +166,13 @@ def register_arguments(parser: ArgumentParser, runners: List[RunnerModule], exec runtime.add_argument( "--envdir", metavar = "", - help = "Set environment variables from the envdir at . " + help = "Set environment variables from the envdir at ````. " "May be specified more than once. " "An envdir is a directory containing files describing environment variables. " "Each filename is used as the variable name. " "The first line of the contents of each file is used as the variable value. " - "When this option or --env is given, the default behaviour of automatically passing thru several \"well-known\" variables is disabled. " - "See the description of --env for more details. " + "When this option or :option:`--env` is given, the default behaviour of automatically passing thru several \"well-known\" variables is disabled. " + "See the description of :option:`--env` for more details. " f"{SKIP_AUTO_DEFAULT_IN_HELP}", type = DirectoryPath, action = "append", diff --git a/nextstrain/cli/runner/conda.py b/nextstrain/cli/runner/conda.py index 077e7525..0d181444 100644 --- a/nextstrain/cli/runner/conda.py +++ b/nextstrain/cli/runner/conda.py @@ -6,7 +6,7 @@ ===================== .. warning:: - For development only. You don't need to set this during normal operation. + For development only. You don't need to set these during normal operation. .. envvar:: NEXTSTRAIN_CONDA_CHANNEL diff --git a/setup.py b/setup.py index 99678a32..dbc4ff32 100644 --- a/setup.py +++ b/setup.py @@ -133,7 +133,6 @@ def find_namespaced_packages(namespace): "pytest-forked", "recommonmark", "sphinx>=3", - "sphinx-argparse ~=0.3", "sphinx-autobuild", "sphinx-markdown-tables !=0.0.16", "sphinx_rtd_theme", diff --git a/tests/doc.py b/tests/doc.py new file mode 100644 index 00000000..7bbf8dc5 --- /dev/null +++ b/tests/doc.py @@ -0,0 +1,12 @@ +import os +import pytest +from pathlib import Path +from subprocess import run + +topdir = Path(__file__).resolve().parent.parent + +@pytest.mark.skipif(os.name != "posix", reason = "devel/generate-command-doc requires a POSIX platform") +def pytest_generated_command_doc(): + # Check the exit status ourselves for nicer test output on failure + result = run([topdir / "devel/generate-command-doc", "--check", "--diff"]) + assert result.returncode == 0, f"{result.args!r} exited with errors" diff --git a/tests/help.py b/tests/help.py index 45f2b8d4..48d81ef2 100644 --- a/tests/help.py +++ b/tests/help.py @@ -1,33 +1,27 @@ import pytest -import argparse import os -from itertools import chain from nextstrain.cli import make_parser +from nextstrain.cli.argparse import walk_commands from subprocess import run -def walk_commands(command, parser): - yield command +def generate_commands(): + for command, parser in walk_commands(make_parser()): + has_extended_help = any( + any(opt == "--help-all" for opt in action.option_strings) + for action in parser._actions) - subparsers = chain.from_iterable( - action.choices.items() - for action in parser._actions - if isinstance(action, argparse._SubParsersAction)) + yield (*command, "--help-all" if has_extended_help else "--help") - for subname, subparser in subparsers: - yield from walk_commands([*command, subname], subparser) - -commands = list(walk_commands(("nextstrain",), make_parser())) +commands = list(generate_commands()) @pytest.mark.parametrize("command", commands, ids = lambda command: " ".join(command)) def pytest_help(command): # Check the exit status ourselves for nicer test output on failure - argv = (*command, "--help") - - result = run(argv) - assert result.returncode == 0, f"{argv} exited with error" + result = run(command) + assert result.returncode == 0, f"{command} exited with error" - result = run(argv, env = {**os.environ, "NEXTSTRAIN_RST_STRICT": "yes"}) - assert result.returncode == 0, f"{argv} exited with error with strict rST conversion" + result = run(command, env = {**os.environ, "NEXTSTRAIN_RST_STRICT": "yes"}) + assert result.returncode == 0, f"{command} exited with error with strict rST conversion"