diff --git a/src/setuptools_scm/__main__.py b/src/setuptools_scm/__main__.py index dab6068a..3f56d42a 100644 --- a/src/setuptools_scm/__main__.py +++ b/src/setuptools_scm/__main__.py @@ -3,4 +3,4 @@ from ._cli import main if __name__ == "__main__": - main() + raise SystemExit(main()) diff --git a/src/setuptools_scm/_cli.py b/src/setuptools_scm/_cli.py index 66099b12..d3f11c81 100644 --- a/src/setuptools_scm/_cli.py +++ b/src/setuptools_scm/_cli.py @@ -1,8 +1,10 @@ from __future__ import annotations import argparse +import json import os import sys +from typing import Any from setuptools_scm import Configuration from setuptools_scm._file_finders import find_files @@ -10,7 +12,7 @@ from setuptools_scm.discover import walk_potential_roots -def main(args: list[str] | None = None) -> None: +def main(args: list[str] | None = None) -> int: opts = _get_cli_opts(args) inferred_root: str = opts.root or "." @@ -36,11 +38,12 @@ def main(args: list[str] | None = None) -> None: raise SystemExit("ERROR: no version found for", opts) if opts.strip_dev: version = version.partition(".dev")[0] - print(version) if opts.command == "ls": - for fname in find_files(config.root): - print(fname) + return ls_command(opts, version, config) + + print(version) + return 0 def _get_cli_opts(args: list[str] | None) -> argparse.Namespace: @@ -69,11 +72,96 @@ def _get_cli_opts(args: list[str] | None) -> argparse.Namespace: ) sub = parser.add_subparsers(title="extra commands", dest="command", metavar="") # We avoid `metavar` to prevent printing repetitive information - desc = "List files managed by the SCM" - sub.add_parser("ls", help=desc[0].lower() + desc[1:], description=desc) + desc = "List information about the package, e.g. included files" + ls_parser = sub.add_parser("ls", help=desc[0].lower() + desc[1:], description=desc) + ls_parser.add_argument( + "-N", + "--no-version", + action="store_true", + help="do not include package version in the output", + ) + output_formats = ["json", "plain", "key-value"] + ls_parser.add_argument( + "-f", + "--format", + type=str.casefold, + default="plain", + help="specify output format", + choices=output_formats, + ) + ls_parser.add_argument( + "-q", + "--query", + type=str.casefold, + nargs="*", + help="display setuptools_scm settings according to query, " + "e.g. dist_name, do not supply an argument in order to " + "print a list of valid queries.", + ) return parser.parse_args(args) +# flake8: noqa: C901 +def ls_command(opts: argparse.Namespace, version: str, config: Configuration) -> int: + data = {} + + if opts.query == []: + opts.no_version = True + sys.stderr.write("Available queries:\n\n") + opts.query = ["queries"] + data["queries"] = ["files"] + list(config.__dataclass_fields__.keys()) + + if opts.query is None: + opts.query = ["files"] + + if "files" in opts.query: + data["files"] = find_files(config.root) + + for q in opts.query: + if q in ["files", "queries"]: + continue + + try: + if q.startswith("_"): + raise AttributeError() + data[q] = getattr(config, q) + except AttributeError: + sys.stderr.write(f"Error: unknown query: '{q}'\n") + return 1 + + if opts.format == "json": + print(json.dumps(data, indent=2)) + + if opts.format == "plain": + _print_plain(opts, data, version) + + if opts.format == "key-value": + _print_key_value(data) + + return 0 + + +def _print_plain(opts: argparse.Namespace, data: dict[Any, Any], version: str) -> None: + if opts.no_version is False: + print(version) + files = data.pop("files", []) + for file_ in files: + print(file_) + queries = data.pop("queries", []) + for query in queries: + print(query) + print("\n".join(data.values())) + + +def _print_key_value(data: dict[Any, Any]) -> None: + for key, value in data.items(): + if isinstance(value, str): + print(f"{key} = {value}") + else: + str_value = "\n ".join(value) + print(f"{key} = {str_value}") + + def _find_pyproject(parent: str) -> str: for directory in walk_potential_roots(os.path.abspath(parent)): pyproject = os.path.join(directory, "pyproject.toml")