diff --git a/README.md b/README.md index f21cce0..b1d9870 100644 --- a/README.md +++ b/README.md @@ -149,8 +149,7 @@ positional arguments: {pp,plot,ls} Available subcommands pp Pretty print a JSON log. plot Plot resource usage for an execution. - ls Print execution information for all runs matching - DUCT_OUTPUT_PREFIX. + ls Print execution information for all matching runs. options: -h, --help show this help message and exit diff --git a/src/con_duct/suite/ls.py b/src/con_duct/suite/ls.py index f3c9e03..90a2f10 100644 --- a/src/con_duct/suite/ls.py +++ b/src/con_duct/suite/ls.py @@ -1,5 +1,6 @@ import argparse from collections import OrderedDict +import glob import json import logging from typing import Any, Dict, List, Optional @@ -10,7 +11,7 @@ except ImportError: pyout = None import yaml -from con_duct.__main__ import SummaryFormatter +from con_duct.__main__ import DUCT_OUTPUT_PREFIX, SummaryFormatter lgr = logging.getLogger(__name__) @@ -141,6 +142,11 @@ def pyout_ls(run_data_list: List[OrderedDict[str, Any]]) -> None: def ls(args: argparse.Namespace) -> int: + + if not args.paths: + pattern = f"{DUCT_OUTPUT_PREFIX[:DUCT_OUTPUT_PREFIX.index('{')]}*" + args.paths = [p for p in glob.glob(pattern)] + info_files = [path for path in args.paths if path.endswith("info.json")] run_data_raw = load_duct_runs(info_files) formatter = SummaryFormatter(enable_colors=args.colors) diff --git a/src/con_duct/suite/main.py b/src/con_duct/suite/main.py index 4906513..3b62cee 100644 --- a/src/con_duct/suite/main.py +++ b/src/con_duct/suite/main.py @@ -2,7 +2,6 @@ import os import sys from typing import List, Optional -from con_duct.__main__ import DUCT_OUTPUT_PREFIX from con_duct.suite.ls import LS_FIELD_CHOICES, ls from con_duct.suite.plot import matplotlib_plot from con_duct.suite.pprint_json import pprint_json @@ -51,7 +50,7 @@ def main(argv: Optional[List[str]] = None) -> None: parser_ls = subparsers.add_parser( "ls", - help="Print execution information for all runs matching DUCT_OUTPUT_PREFIX.", + help="Print execution information for all matching runs.", ) parser_ls.add_argument( "-f", @@ -85,8 +84,8 @@ def main(argv: Optional[List[str]] = None) -> None: parser_ls.add_argument( "paths", nargs="*", - default=[f"{DUCT_OUTPUT_PREFIX[:DUCT_OUTPUT_PREFIX.index('{')]}*"], - help="Path to duct report files, only `info.json` would be considered.", + help="Path to duct report files, only `info.json` would be considered. " + "If not provided, the program will glob for files that match DUCT_OUTPUT_PREFIX.", ) parser_ls.set_defaults(func=ls) diff --git a/test/test_suite.py b/test/test_suite.py index 673a977..1ae3589 100644 --- a/test/test_suite.py +++ b/test/test_suite.py @@ -149,6 +149,8 @@ class TestLS(unittest.TestCase): def setUp(self) -> None: """Create a temporary directory and test files.""" self.temp_dir = tempfile.TemporaryDirectory() + self.old_cwd = os.getcwd() + os.chdir(self.temp_dir.name) self.files = { "file1_info.json": { "schema_version": MINIMUM_SCHEMA_VERSION, @@ -163,13 +165,20 @@ def setUp(self) -> None: "schema_version": MINIMUM_SCHEMA_VERSION, "prefix": "no_match", }, + ".duct/logs/default_logpath_info.json": { + "schema_version": MINIMUM_SCHEMA_VERSION, + "prefix": "default_file1", + }, } for filename, content in self.files.items(): - with open(os.path.join(self.temp_dir.name, filename), "w") as f: + full_path = os.path.join(self.temp_dir.name, filename) + os.makedirs(os.path.dirname(full_path), exist_ok=True) + with open(full_path, "w") as f: json.dump(content, f) def tearDown(self) -> None: """Clean up the temporary directory.""" + os.chdir(self.old_cwd) self.temp_dir.cleanup() def _run_ls(self, paths: list[str], fmt: str) -> str: @@ -201,6 +210,23 @@ def test_ls_sanity(self) -> None: assert len(prefixes) == 1 assert any("file1" in p for p in prefixes) + def test_ls_no_pos_args(self) -> None: + result = self._run_ls([], "summaries") + + assert "Prefix:" in result + prefixes = [ + line.split(":", 1)[1].strip() + for line in result.splitlines() + if line.startswith("Prefix:") + ] + assert len(prefixes) == 1 + assert any("default_logpath" in p for p in prefixes) + + assert "file1" not in result + assert "file2" not in result + assert "file3" not in result + assert "not_matching.json" not in result + def test_ls_multiple_paths(self) -> None: """Basic sanity test to ensure ls() runs without crashing.""" files_1_and_2 = ["file1_info.json", "file2_info.json"]