diff --git a/borgmatic/commands/arguments.py b/borgmatic/commands/arguments.py index 09ec0baa..a043ae6c 100644 --- a/borgmatic/commands/arguments.py +++ b/borgmatic/commands/arguments.py @@ -307,7 +307,7 @@ def make_argument_description(schema, flag_name): if '[0]' in flag_name: description += ' To specify a different list element, replace the "[0]" with another array index ("[1]", "[2]", etc.).' - if example and schema_type == 'array': + if example and schema_type in ('array', 'object'): example_buffer = io.StringIO() yaml = ruamel.yaml.YAML(typ='safe') yaml.default_flow_style = True @@ -453,13 +453,15 @@ def add_arguments_from_schema(arguments_group, schema, unparsed_arguments, names if schema_type == 'object': properties = schema.get('properties') + # If there are child properties, recurse for each one. But if there are no child properties, + # fall through so that a flag gets added below for the (empty) object. if properties: for name, child in properties.items(): add_arguments_from_schema( arguments_group, child, unparsed_arguments, names + (name,) ) - return + return # If this is an "array" type, recurse for each items type child option. Don't return yet so that # a flag also gets added below for the array itself. @@ -485,9 +487,9 @@ def add_arguments_from_schema(arguments_group, schema, unparsed_arguments, names metavar = names[-1].upper() description = make_argument_description(schema, flag_name) - # array=str instead of list here to support specifying a list as a YAML string on the + # The ...=str given here is to support specifying an object or an array as a YAML string on the # command-line. - argument_type = borgmatic.config.schema.parse_type(schema_type, array=str) + argument_type = borgmatic.config.schema.parse_type(schema_type, object=str, array=str) full_flag_name = f"--{flag_name.replace('_', '-')}" # As a UX nicety, allow boolean options that have a default of false to have command-line flags diff --git a/tests/integration/commands/test_arguments.py b/tests/integration/commands/test_arguments.py index a357e1b0..1a4ee87d 100644 --- a/tests/integration/commands/test_arguments.py +++ b/tests/integration/commands/test_arguments.py @@ -4,6 +4,22 @@ from flexmock import flexmock from borgmatic.commands import arguments as module +def test_make_argument_description_with_object_adds_example(): + assert ( + module.make_argument_description( + schema={ + 'description': 'Thing.', + 'type': 'object', + 'example': {'bar': 'baz'}, + }, + flag_name='flag', + ) + # Apparently different versions of ruamel.yaml serialize this + # differently. + in ('Thing. Example value: "bar: baz"' 'Thing. Example value: "{bar: baz}"') + ) + + def test_make_argument_description_with_array_adds_example(): assert ( module.make_argument_description( diff --git a/tests/unit/commands/test_arguments.py b/tests/unit/commands/test_arguments.py index 1e5bbe0b..bf1b4f23 100644 --- a/tests/unit/commands/test_arguments.py +++ b/tests/unit/commands/test_arguments.py @@ -590,6 +590,42 @@ def test_make_argument_description_without_description_bails(): ) +def test_make_argument_description_with_object_adds_example(): + buffer = flexmock() + buffer.should_receive('getvalue').and_return('{foo: example}') + flexmock(module.io).should_receive('StringIO').and_return(buffer) + yaml = flexmock() + yaml.should_receive('dump') + flexmock(module.ruamel.yaml).should_receive('YAML').and_return(yaml) + + assert ( + module.make_argument_description( + schema={ + 'description': 'Thing.', + 'type': 'object', + 'example': {'foo': 'example'}, + }, + flag_name='flag', + ) + == 'Thing. Example value: "{foo: example}"' + ) + + +def test_make_argument_description_with_object_skips_missing_example(): + flexmock(module.ruamel.yaml).should_receive('YAML').never() + + assert ( + module.make_argument_description( + schema={ + 'description': 'Thing.', + 'type': 'object', + }, + flag_name='flag', + ) + == 'Thing.' + ) + + def test_make_argument_description_with_array_adds_example(): buffer = flexmock() buffer.should_receive('getvalue').and_return('[example]') @@ -612,9 +648,7 @@ def test_make_argument_description_with_array_adds_example(): def test_make_argument_description_with_array_skips_missing_example(): - yaml = flexmock() - yaml.should_receive('dump').and_return('[example]') - flexmock(module.ruamel.yaml).should_receive('YAML').and_return(yaml) + flexmock(module.ruamel.yaml).should_receive('YAML').never() assert ( module.make_argument_description( @@ -951,12 +985,17 @@ def test_add_arguments_from_schema_with_empty_multi_type_raises(): ) -def test_add_arguments_from_schema_with_propertyless_option_does_not_add_flag(): +def test_add_arguments_from_schema_with_propertyless_option_adds_flag(): arguments_group = flexmock() - flexmock(module).should_receive('make_argument_description').never() - flexmock(module.borgmatic.config.schema).should_receive('parse_type').never() - arguments_group.should_receive('add_argument').never() - flexmock(module).should_receive('add_array_element_arguments').never() + flexmock(module).should_receive('make_argument_description').and_return('help') + flexmock(module.borgmatic.config.schema).should_receive('parse_type').and_return(str) + arguments_group.should_receive('add_argument').with_args( + '--foo', + type=str, + metavar='FOO', + help='help', + ).once() + flexmock(module).should_receive('add_array_element_arguments') module.add_arguments_from_schema( arguments_group=arguments_group,