Browse Source

[2254] Fix tab-completion in lists

Jelte Jansen 12 years ago
parent
commit
483011e9e1

+ 5 - 1
src/bin/bindctl/bindcmd.py

@@ -497,7 +497,11 @@ class BindCmdInterpreter(Cmd):
                         list = self.config_data.get_config_item_list(my_text.rpartition("/")[0], True)
                         list = self.config_data.get_config_item_list(my_text.rpartition("/")[0], True)
                         hints.extend([val for val in list if val.startswith(my_text[1:])])
                         hints.extend([val for val in list if val.startswith(my_text[1:])])
                         # remove the common prefix from the hints so we don't get it twice
                         # remove the common prefix from the hints so we don't get it twice
-                        hints = self.remove_prefix(hints, my_text.rpartition("/")[0])
+                        prefix, _, rest = my_text.rpartition("/")
+                        hints = self.remove_prefix(hints, prefix)
+                        # And prevent 'double addition' by also removing final
+                        # part matches
+                        hints = [ h for h in hints if h != rest ]
             except CmdModuleNameFormatError:
             except CmdModuleNameFormatError:
                 if not text:
                 if not text:
                     hints = self.get_module_names()
                     hints = self.get_module_names()

+ 35 - 5
src/lib/python/isc/config/config_data.py

@@ -25,6 +25,8 @@ import isc.config.module_spec
 import ast
 import ast
 import copy
 import copy
 
 
+import traceback
+
 class ConfigDataError(Exception): pass
 class ConfigDataError(Exception): pass
 
 
 BIND10_CONFIG_DATA_VERSION = 2
 BIND10_CONFIG_DATA_VERSION = 2
@@ -45,6 +47,13 @@ def spec_part_is_named_set(spec_part):
        named_set specification, and False otherwise."""
        named_set specification, and False otherwise."""
     return (type(spec_part) == dict and 'named_set_item_spec' in spec_part)
     return (type(spec_part) == dict and 'named_set_item_spec' in spec_part)
 
 
+def spec_part_is_any(spec_part):
+    """Returns true if the given spec_part specifies an element of type
+       any, and False otherwise.
+    """
+    return (type(spec_part) == dict and 'item_type' in spec_part and
+            spec_part['item_type'] == "any")
+
 def check_type(spec_part, value):
 def check_type(spec_part, value):
     """Does nothing if the value is of the correct type given the
     """Does nothing if the value is of the correct type given the
        specification part relevant for the value. Raises an
        specification part relevant for the value. Raises an
@@ -237,7 +246,8 @@ def spec_name_list(spec, prefix="", recurse=False):
         elif 'named_set_item_spec' in spec:
         elif 'named_set_item_spec' in spec:
             # we added a '/' above, but in this one case we don't want it
             # we added a '/' above, but in this one case we don't want it
             result.append(prefix[:-1])
             result.append(prefix[:-1])
-        else:
+        # ignore any
+        elif not spec_part_is_any(spec):
             for name in spec:
             for name in spec:
                 result.append(prefix + name + "/")
                 result.append(prefix + name + "/")
                 if recurse:
                 if recurse:
@@ -392,14 +402,25 @@ class MultiConfigData:
            identifier, or None if not found. The first part of the
            identifier, or None if not found. The first part of the
            identifier (up to the first /) is interpreted as the module
            identifier (up to the first /) is interpreted as the module
            name. Returns None if not found, or if identifier is not a
            name. Returns None if not found, or if identifier is not a
-           string."""
+           string.
+           If an index is given for a List-type element, it returns
+           the specification of the list elements, not of the list itself
+           """
         if type(identifier) != str or identifier == "":
         if type(identifier) != str or identifier == "":
             return None
             return None
         if identifier[0] == '/':
         if identifier[0] == '/':
             identifier = identifier[1:]
             identifier = identifier[1:]
         module, sep, id = identifier.partition("/")
         module, sep, id = identifier.partition("/")
+        if id != "":
+            id, indices = isc.cc.data.split_identifier_list_indices(id)
+        else:
+            indices = None
         try:
         try:
-            return find_spec_part(self._specifications[module].get_config_spec(), id)
+            spec_part = find_spec_part(self._specifications[module].get_config_spec(), id)
+            if indices is not None and spec_part_is_list(spec_part):
+                return spec_part['list_item_spec']
+            else:
+                return spec_part
         except isc.cc.data.DataNotFoundError as dnfe:
         except isc.cc.data.DataNotFoundError as dnfe:
             return None
             return None
         except KeyError as ke:
         except KeyError as ke:
@@ -782,8 +803,7 @@ class MultiConfigData:
            return a list of those (appended to item_name), otherwise
            return a list of those (appended to item_name), otherwise
            the list will only contain the item_name itself."""
            the list will only contain the item_name itself."""
         spec_part = self.find_spec_part(item_name)
         spec_part = self.find_spec_part(item_name)
-        if 'item_type' in spec_part and \
-           spec_part['item_type'] == 'named_set':
+        if spec_part_is_named_set(spec_part):
             subslash = ""
             subslash = ""
             if spec_part['named_set_item_spec']['item_type'] == 'map' or\
             if spec_part['named_set_item_spec']['item_type'] == 'map' or\
                spec_part['named_set_item_spec']['item_type'] == 'named_set':
                spec_part['named_set_item_spec']['item_type'] == 'named_set':
@@ -793,6 +813,16 @@ class MultiConfigData:
                 return [ item_name + "/" + v + subslash for v in values.keys() ]
                 return [ item_name + "/" + v + subslash for v in values.keys() ]
             else:
             else:
                 return [ item_name ]
                 return [ item_name ]
+        elif spec_part_is_list(spec_part):
+            values, status = self.get_value(item_name)
+            if len(values) > 0:
+                result = []
+                for i in range(len(values)):
+                    name = item_name + '[%d]' % i
+                    result.extend(self._get_list_items(name))
+                return result
+            else:
+                return [ item_name ]
         else:
         else:
             return [ item_name ]
             return [ item_name ]
 
 

+ 4 - 4
src/lib/python/isc/config/tests/config_data_test.py

@@ -731,13 +731,13 @@ class TestMultiConfigData(unittest.TestCase):
         config_items = self.mcd.get_config_item_list(None, True)
         config_items = self.mcd.get_config_item_list(None, True)
         self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
         self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
         config_items = self.mcd.get_config_item_list("Spec2", True)
         config_items = self.mcd.get_config_item_list("Spec2", True)
-        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
+        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5[0]', 'Spec2/item5[1]', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
         config_items = self.mcd.get_config_item_list("Spec2")
         config_items = self.mcd.get_config_item_list("Spec2")
-        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6'], config_items)
+        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5[0]', 'Spec2/item5[1]', 'Spec2/item6'], config_items)
         config_items = self.mcd.get_config_item_list("/Spec2")
         config_items = self.mcd.get_config_item_list("/Spec2")
-        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6'], config_items)
+        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5[0]', 'Spec2/item5[1]', 'Spec2/item6'], config_items)
         config_items = self.mcd.get_config_item_list("Spec2", True)
         config_items = self.mcd.get_config_item_list("Spec2", True)
-        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
+        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5[0]', 'Spec2/item5[1]', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
 
 
     def test_is_named_set(self):
     def test_is_named_set(self):
         module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec32.spec")
         module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec32.spec")