Repository: warpnet/salt-lint

Issue:
String representing curly braces is incorrectly detected
**Describe the bug**

In a string with curly brace-notation for standard string formatting it is necessary to use double curly braces in order to output a pair of single curly braces.

The following snippet is an extract from a jinja file to format an ldap search pattern.

```jinja
{% set search_filter="(&({}={{0}})({}=CN={},{}))".format(string1, string2, string3, string4)) %}
```

The output should be (assuming the string contents are the same as the variable name) like so:

```python
search_filter="(&(string1={0})(string2=CN=string3,string4))"
```
However, salt-lint picks up the double braces as a jinja replacement. In this case, jinja will not replace, it formats the string as expected.

Quite a conundrum

**To Reproduce**

Use the string identified above in a `jinja` file and then run salt-lint.

**Expected behavior**

The string format to pass lint.

**Desktop (please complete the following information):**

any



Discussion:

Patch:
diff --git a/saltlint/linter.py b/saltlint/linter.py
index 44951d9..83e12be 100644
--- a/saltlint/linter.py
+++ b/saltlint/linter.py
@@ -79,7 +79,8 @@ class RulesCollection(object):
         self.config = config
 
     def register(self, obj):
-        self.rules.append(obj)
+        if not any(rule.id == obj.id for rule in self.rules):
+            self.rules.append(obj)
 
     def __iter__(self):
         return iter(self.rules)
diff --git a/saltlint/rules/JinjaVariableHasSpacesRule.py b/saltlint/rules/JinjaVariableHasSpacesRule.py
index a0c5fd8..0941753 100644
--- a/saltlint/rules/JinjaVariableHasSpacesRule.py
+++ b/saltlint/rules/JinjaVariableHasSpacesRule.py
@@ -15,7 +15,7 @@ class JinjaVariableHasSpacesRule(SaltLintRule):
     tags = ['formatting', 'jinja']
     version_added = 'v0.0.1'
 
-    bracket_regex = re.compile(r"{{[^ \-\+]|{{[-\+][^ ]|[^ \-\+]}}|[^ ][-\+]}}")
+    bracket_regex = re.compile(r"{{[^ \-\+\d]|{{[-\+][^ ]|[^ \-\+\d]}}|[^ {][-\+\d]}}")
 
     def match(self, file, line):
         return self.bracket_regex.search(line)


Test patch:
diff --git a/tests/unit/TestJinjaVariableHasSpaces.py b/tests/unit/TestJinjaVariableHasSpaces.py
index a066818..1fd0448 100644
--- a/tests/unit/TestJinjaVariableHasSpaces.py
+++ b/tests/unit/TestJinjaVariableHasSpaces.py
@@ -18,7 +18,27 @@ BAD_VARIABLE_LINE = '''
 {{-variable+}}
 '''
 
-class TestLineTooLongRule(unittest.TestCase):
+BAD_VARIABLE_ENDING_IN_INTEGER = '''
+{{-variable0+}}
+'''
+
+BAD_VARIABLE_ENDING_IN_INTEGER_RIGHT = '''
+{{ variable0}}
+'''
+
+DOUBLE_QUOTED_INTEGER_IS_VALID = '''
+{{ "{{0}}" }}
+'''
+
+DOUBLE_QUOTED_INTEGER_TRAILING_SPACE_IS_INVALID = '''
+{{ "{{0}}"}}
+'''
+
+DOUBLE_QUOTED_INTEGER_LEADING_SPACE_IS_INVALID = '''
+{{"{{0}}" }}
+'''
+
+class TestJinjaVariableHasSpaces(unittest.TestCase):
     collection = RulesCollection()
 
     def setUp(self):
@@ -32,3 +52,23 @@ class TestLineTooLongRule(unittest.TestCase):
     def test_statement_negative(self):
         results = self.runner.run_state(BAD_VARIABLE_LINE)
         self.assertEqual(1, len(results))
+
+    def test_double_quoted_integer(self):
+        results = self.runner.run_state(DOUBLE_QUOTED_INTEGER_IS_VALID)
+        self.assertEqual(0, len(results))
+
+    def test_double_quoted_integer_trailing_space_invalid(self):
+        results = self.runner.run_state(DOUBLE_QUOTED_INTEGER_TRAILING_SPACE_IS_INVALID)
+        self.assertEqual(1, len(results))
+
+    def test_double_quoted_integer_leading_space_invalid(self):
+        results = self.runner.run_state(DOUBLE_QUOTED_INTEGER_LEADING_SPACE_IS_INVALID)
+        self.assertEqual(1, len(results))
+
+    def test_variable_bad_ends_with_integer(self):
+        results = self.runner.run_state(BAD_VARIABLE_ENDING_IN_INTEGER)
+        self.assertEqual(1, len(results))
+
+    def test_variable_bad_ends_with_integer_right(self):
+        results = self.runner.run_state(BAD_VARIABLE_ENDING_IN_INTEGER_RIGHT)
+        self.assertEqual(1, len(results))