Browse Source

Organizational refactoring (#303).

Dan Helfman 2 months ago
parent
commit
f222bf2c1a
3 changed files with 98 additions and 69 deletions
  1. 88 64
      borgmatic/commands/arguments.py
  2. 4 4
      borgmatic/config/arguments.py
  3. 6 1
      borgmatic/config/schema.py

+ 88 - 64
borgmatic/commands/arguments.py

@@ -291,6 +291,91 @@ def parse_arguments_for_actions(unparsed_arguments, action_parsers, global_parse
 OMITTED_FLAG_NAMES = {'match_archives', 'progress', 'stats', 'list'}
 OMITTED_FLAG_NAMES = {'match_archives', 'progress', 'stats', 'list'}
 
 
 
 
+def make_argument_description(schema, flag_name):
+    '''
+    Given a configuration schema dict and a flag name for it, extend the schema's description with
+    an example or additional information as appropriate based on its type. Return the updated
+    description for use in a command-line argument.
+    '''
+    description = schema.get('description')
+    schema_type = schema.get('type')
+
+    if not description:
+        return None
+
+    if schema_type == 'array':
+        example_buffer = io.StringIO()
+        yaml = ruamel.yaml.YAML(typ='safe')
+        yaml.default_flow_style = True
+        yaml.dump(schema.get('example'), example_buffer)
+
+        description += f' Example value: "{example_buffer.getvalue().strip()}"'
+
+    if '[0]' in flag_name:
+        description += ' To specify a different list element, replace the "[0]" with another array index ("[1]", "[2]", etc.).'
+
+    description = description.replace('%', '%%')
+
+
+def add_array_element_arguments_from_schema(arguments_group, schema, unparsed_arguments, flag_name):
+    '''
+    Given an argparse._ArgumentGroup instance, a configuration schema dict, a sequence of unparsed
+    argument strings, and a dotted flag name, convert the schema into corresponding command-line
+    array element flags that correspond to the given unparsed arguments.
+
+    Here's the background. We want to support flags that can have arbitrary indices like:
+    
+      --foo.bar[1].baz
+    
+    But argparse doesn't support that natively because the index can be an arbitrary number. We
+    won't let that stop us though, will we?
+
+    If the current flag name has an array component in it (e.g. a name with "[0]"), then make a
+    pattern that would match the flag name regardless of the number that's in it. The idea is that
+    we want to look for unparsed arguments that appear like the flag name, but instead of "[0]" they
+    have, say, "[1]" or "[123]".
+    
+    Next, we check each unparsed argument against that pattern. If one of them matches, add an
+    argument flag for it to the argument parser group. Example:
+    
+    Let's say flag_name is:
+    
+        --foo.bar[0].baz
+    
+    ... then the regular expression pattern will be:
+    
+        ^--foo\.bar\[\d+\]\.baz
+    
+    ... and, if that matches an unparsed argument of:
+    
+        --foo.bar[1].baz
+    
+    ... then an argument flag will get added equal to that unparsed argument. And the unparsed
+    argument will match it when parsing is performed! In this manner, we're using the actual user
+    CLI input to inform what exact flags we support!
+    '''
+    if '[0]' not in flag_name or '--help' in unparsed_arguments:
+        return
+
+    pattern = re.compile(f'^--{flag_name.replace("[0]", r"\[\d+\]").replace(".", r"\.")}$')
+    existing_flags = set(
+        itertools.chain(
+            *(group_action.option_strings for group_action in arguments_group._group_actions)
+        )
+    )
+
+    for unparsed in unparsed_arguments:
+        unparsed_flag_name = unparsed.split('=', 1)[0]
+
+        if pattern.match(unparsed_flag_name) and unparsed_flag_name not in existing_flags:
+            arguments_group.add_argument(
+                unparsed_flag_name,
+                type=argument_type,
+                metavar=metavar,
+                help=description,
+            )
+
+
 def add_arguments_from_schema(arguments_group, schema, unparsed_arguments, names=None):
 def add_arguments_from_schema(arguments_group, schema, unparsed_arguments, names=None):
     '''
     '''
     Given an argparse._ArgumentGroup instance, a configuration schema dict, and a sequence of
     Given an argparse._ArgumentGroup instance, a configuration schema dict, and a sequence of
@@ -362,28 +447,14 @@ def add_arguments_from_schema(arguments_group, schema, unparsed_arguments, names
                 )
                 )
 
 
     flag_name = '.'.join(names)
     flag_name = '.'.join(names)
-    description = schema.get('description')
     metavar = names[-1].upper()
     metavar = names[-1].upper()
 
 
-    if description:
-        if schema_type == 'array':
-            example_buffer = io.StringIO()
-            yaml = ruamel.yaml.YAML(typ='safe')
-            yaml.default_flow_style = True
-            yaml.dump(schema.get('example'), example_buffer)
-
-            description += f' Example value: "{example_buffer.getvalue().strip()}"'
-
-        if '[0]' in flag_name:
-            description += ' To specify a different list element, replace the "[0]" with another array index ("[1]", "[2]", etc.).'
-
-        description = description.replace('%', '%%')
-
-    # These options already have corresponding flags on individual actions (like "create
+    # Certain options already have corresponding flags on individual actions (like "create
     # --progress"), so don't bother adding them to the global flags.
     # --progress"), so don't bother adding them to the global flags.
     if flag_name in OMITTED_FLAG_NAMES:
     if flag_name in OMITTED_FLAG_NAMES:
         return
         return
 
 
+    description = make_argument_description(schema, flag_name)
     argument_type = borgmatic.config.schema.parse_type(schema_type)
     argument_type = borgmatic.config.schema.parse_type(schema_type)
     full_flag_name = f"--{flag_name.replace('_', '-')}"
     full_flag_name = f"--{flag_name.replace('_', '-')}"
 
 
@@ -404,54 +475,7 @@ def add_arguments_from_schema(arguments_group, schema, unparsed_arguments, names
             help=description,
             help=description,
         )
         )
 
 
-    # We want to support flags that can have arbitrary indices like:
-    #
-    #   --foo.bar[1].baz
-    #
-    # But argparse doesn't support that natively because the index can be an arbitrary number. We
-    # won't let that stop us though, will we? So, if the current flag name has an array component in
-    # it (e.g. a name with "[0]"), then make a pattern that would match the flag name regardless of
-    # the number that's in it. The idea is that we want to look for unparsed arguments that appear
-    # like the flag name, but instead of "[0]" they have, say, "[1]" or "[123]".
-    #
-    # Next, we check each unparsed argument against that pattern. If one of them matches, add an
-    # argument flag for it to the argument parser group. Example:
-    #
-    # Let's say flag_name is:
-    #
-    #     --foo.bar[0].baz
-    #
-    # ... then the regular expression pattern will be:
-    #
-    #     ^--foo\.bar\[\d+\]\.baz
-    #
-    # ... and, if that matches an unparsed argument of:
-    #
-    #     --foo.bar[1].baz
-    #
-    # ... then an argument flag will get added equal to that unparsed argument. And the unparsed
-    # argument will match it when parsing is performed! In this manner, we're using the actual user
-    # CLI input to inform what exact flags we support!
-    if '[0]' not in flag_name or '--help' in unparsed_arguments:
-        return
-
-    pattern = re.compile(f'^--{flag_name.replace("[0]", r"\[\d+\]").replace(".", r"\.")}$')
-    existing_flags = set(
-        itertools.chain(
-            *(group_action.option_strings for group_action in arguments_group._group_actions)
-        )
-    )
-
-    for unparsed in unparsed_arguments:
-        unparsed_flag_name = unparsed.split('=', 1)[0]
-
-        if pattern.match(unparsed_flag_name) and unparsed_flag_name not in existing_flags:
-            arguments_group.add_argument(
-                unparsed_flag_name,
-                type=argument_type,
-                metavar=metavar,
-                help=description,
-            )
+    add_array_element_arguments_from_schema(arguments_group, schema, unparsed_arguments, flag_name)
 
 
 
 
 def make_parsers(schema, unparsed_arguments):
 def make_parsers(schema, unparsed_arguments):

+ 4 - 4
borgmatic/config/arguments.py

@@ -14,13 +14,13 @@ def set_values(config, keys, value):
 
 
         ('foo', 'bar', 'baz')
         ('foo', 'bar', 'baz')
 
 
-    This looks up "foo" in the given configuration. And within that value, it looks up "bar". And
-    then within that value, it looks up "baz" and sets it to the given value. Another example:
+    This looks up "foo" in the given configuration dict. And within that, it looks up "bar". And
+    then within that, it looks up "baz" and sets it to the given value. Another example:
 
 
         ('mylist[0]', 'foo')
         ('mylist[0]', 'foo')
 
 
-    This looks for the zeroth element of "mylist" in the given configuration. And within that value,
-    it looks up "foo" and sets it to the given value.
+    This looks for the zeroth element of "mylist" in the given configuration. And within that, it
+    looks up "foo" and sets it to the given value.
     '''
     '''
     if not keys:
     if not keys:
         return
         return

+ 6 - 1
borgmatic/config/schema.py

@@ -5,7 +5,7 @@ import itertools
 def get_properties(schema):
 def get_properties(schema):
     '''
     '''
     Given a schema dict, return its properties. But if it's got sub-schemas with multiple different
     Given a schema dict, return its properties. But if it's got sub-schemas with multiple different
-    potential properties, returned their merged properties instead (interleaved so the first
+    potential properties, return their merged properties instead (interleaved so the first
     properties of each sub-schema come first). The idea is that the user should see all possible
     properties of each sub-schema come first). The idea is that the user should see all possible
     options even if they're not all possible together.
     options even if they're not all possible together.
     '''
     '''
@@ -24,6 +24,11 @@ def get_properties(schema):
 
 
 
 
 def parse_type(schema_type):
 def parse_type(schema_type):
+    '''
+    Given a schema type as a string, return the corresponding Python type.
+
+    Raise ValueError if the schema type is unknown.
+    '''
     try:
     try:
         return {
         return {
             'string': str,
             'string': str,