diff --git a/dvc/command/config.py b/dvc/command/config.py index f536dd04c..5a0c06fed 100644 --- a/dvc/command/config.py +++ b/dvc/command/config.py @@ -3,6 +3,7 @@ import logging from dvc.command.base import CmdBaseNoRepo, append_doc_link from dvc.config import Config, ConfigError +from dvc.utils.flatten import flatten logger = logging.getLogger(__name__) @@ -14,6 +15,22 @@ class CmdConfig(CmdBaseNoRepo): self.config = Config(validate=False) def run(self): + if self.args.list: + if any((self.args.name, self.args.value, self.args.unset)): + logger.error( + "-l/--list can't be used together with any of these " + "options: -u/--unset, name, value" + ) + return 1 + + conf = self.config.load_one(self.args.level) + logger.info("\n".join(self._format_config(conf))) + return 0 + + if self.args.name is None: + logger.error("name argument is required") + return 1 + section, opt = self.args.name.lower().strip().split(".", 1) if self.args.value is None and not self.args.unset: @@ -48,6 +65,11 @@ class CmdConfig(CmdBaseNoRepo): msg = "option {} doesn't exist" raise ConfigError(msg.format(self.args.name)) + @staticmethod + def _format_config(config): + for key, value in flatten(config).items(): + yield f"{key}={value}" + parent_config_parser = argparse.ArgumentParser(add_help=False) level_group = parent_config_parser.add_mutually_exclusive_group() @@ -92,6 +114,13 @@ def add_parser(subparsers, parent_parser): action="store_true", help="Unset option.", ) - config_parser.add_argument("name", help="Option name.") + config_parser.add_argument("name", nargs="?", help="Option name.") config_parser.add_argument("value", nargs="?", help="Option value.") + config_parser.add_argument( + "-l", + "--list", + default=False, + action="store_true", + help="list all defined config values", + ) config_parser.set_defaults(func=CmdConfig) diff --git a/tests/func/test_cli.py b/tests/func/test_cli.py index c36e7f25c..8dc46bc26 100644 --- a/tests/func/test_cli.py +++ b/tests/func/test_cli.py @@ -151,6 +151,13 @@ class TestConfig(TestDvc): self.assertEqual(args.name, name) self.assertEqual(args.value, value) + def test_config_list(self): + args = parse_args(["config", "--list"]) + + self.assertTrue(args.list) + self.assertIsNone(args.name) + self.assertIsNone(args.value) + class TestCheckout(TestDvc): def test(self): diff --git a/tests/func/test_config.py b/tests/func/test_config.py index 42469fd48..91e44643d 100644 --- a/tests/func/test_config.py +++ b/tests/func/test_config.py @@ -60,6 +60,9 @@ class TestConfigCLI(TestDvc): self.assertEqual(ret, 0) self.assertFalse(self._contains(section, field, value, local)) + ret = main(base + ["--list"]) + self.assertEqual(ret, 0) + def test(self): self._do_test(False) @@ -85,6 +88,16 @@ class TestConfigCLI(TestDvc): ret = main(["config", "core.non_existing_field", "-u"]) self.assertEqual(ret, 251) + def test_invalid_config_list(self): + ret = main(["config"]) + self.assertEqual(ret, 1) + + ret = main(["config", "--list", "core.analytics"]) + self.assertEqual(ret, 1) + + ret = main(["config", "--list", "-u"]) + self.assertEqual(ret, 1) + def test_set_invalid_key(dvc): with pytest.raises(ConfigError, match=r"extra keys not allowed"): diff --git a/tests/unit/command/test_config.py b/tests/unit/command/test_config.py new file mode 100644 index 000000000..94f3538d9 --- /dev/null +++ b/tests/unit/command/test_config.py @@ -0,0 +1,20 @@ +from dvc.command.config import CmdConfig + + +def test_config_formatter(): + example_config = { + "section_foo": {"option_bar": True, "option_baz": False}, + "section_foo2": { + "option_bar2": {"option_baz2": True}, + "option_baz3": {"option_baz4": False}, + }, + "section_foo3": {}, + } + + config_lines = tuple(CmdConfig._format_config(example_config)) + assert config_lines == ( + "section_foo.option_bar=True", + "section_foo.option_baz=False", + "section_foo2.option_bar2.option_baz2=True", + "section_foo2.option_baz3.option_baz4=False", + )