We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Adding new CloudFormationLintRule's requires the filename to be identical to the Class name
*cfn-lint version: (`cfn-lint --version`)*
0.0.9

*Description of issue.*

When creating your own rules, you have to add  CloudFormationLintRules, but the loader requires
the python filename to be identical to the Classname.

It would be nice if:
- we can create files with names matching the python convention.
- we could add multiple lint rules in a single module.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/cfnlint/helpers.py`
Content:
```
1 """
2   Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 
4   Permission is hereby granted, free of charge, to any person obtaining a copy of this
5   software and associated documentation files (the "Software"), to deal in the Software
6   without restriction, including without limitation the rights to use, copy, modify,
7   merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
8   permit persons to whom the Software is furnished to do so.
9 
10   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
11   INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
12   PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
13   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
14   OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
15   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16 """
17 import sys
18 import fnmatch
19 import json
20 import os
21 import imp
22 import logging
23 import re
24 import pkg_resources
25 
26 
27 LOGGER = logging.getLogger(__name__)
28 
29 TAG_MAP = 'tag:yaml.org,2002:map'
30 UNCONVERTED_SUFFIXES = ['Ref', 'Condition']
31 FN_PREFIX = 'Fn::'
32 CONDITION_FUNCTIONS = ['Fn::If']
33 REGIONS = ['us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', 'ca-central-1',
34            'eu-central-1', 'eu-west-1', 'eu-west-2', 'ap-northeast-1',
35            'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-south-1',
36            'sa-east-1']
37 
38 REGEX_ALPHANUMERIC = re.compile('^[a-zA-Z0-9]*$')
39 REGEX_CIDR = re.compile(r'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$')
40 REGEX_IPV4 = re.compile(r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$')
41 REGEX_IPV6 = re.compile(r'^(((?=.*(::))(?!.*\3.+\3))\3?|[\dA-F]{1,4}:)([\dA-F]{1,4}(\3|:\b)|\2){5}(([\dA-F]{1,4}(\3|:\b|$)|\2){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})\Z', re.I | re.S)
42 
43 AVAILABILITY_ZONES = [
44     'us-east-1a', 'us-east-1b', 'us-east-1c', 'us-east-1d', 'us-east-1e', 'us-east-1f',
45     'us-east-2a' 'us-east-2b' 'us-east-2c',
46     'us-west-1a', 'us-west-1b', 'us-west-1c',
47     'us-west-2a', 'us-west-2b', 'us-west-2c',
48     'ca-central-1a', 'ca-central-1b',
49     'sa-east-1a', 'sa-east-1b', 'sa-east-1c',
50     'eu-west-1a', 'eu-west-1b', 'eu-west-1c',
51     'eu-west-2a', 'eu-west-2b', 'eu-west-2c',
52     'eu-west-3a', 'eu-west-3b', 'eu-west-3c',
53     'eu-central-1a', 'eu-central-1b', 'eu-central-1c',
54     'eu-central-2a', 'eu-central-2b', 'eu-central-2c',
55     'ap-northeast-1a', 'ap-northeast-1b', 'ap-northeast-1c', 'ap-northeast-1d',
56     'ap-northeast-2a', 'ap-northeast-2b',
57     'ap-northeast-3a',
58     'ap-southeast-1a', 'ap-southeast-1b', 'ap-southeast-1c',
59     'ap-southeast-2a', 'ap-southeast-2b', 'ap-southeast-2c',
60     'ap-south-1a', 'ap-south-1b',
61     'cn-north-1a', 'cn-north-1b',
62 ]
63 
64 FUNCTIONS = [
65     'Fn::Base64', 'Fn::GetAtt', 'Fn::GetAZs', 'Fn::ImportValue',
66     'Fn::Join', 'Fn::Split', 'Fn::FindInMap', 'Fn::Select', 'Ref',
67     'Fn::If', 'Fn::Contains', 'Fn::Sub', 'Fn::Cidr']
68 
69 LIMITS = {
70     'mappings': {
71         'number': 100,
72         'attributes': 64,
73         'name': 255  # in characters
74     },
75     'outputs': {
76         'number': 60,
77         'name': 255  # in characters
78     },
79     'parameters': {
80         'number': 60,
81         'name': 255,  # in characters
82         'value': 4096  # in bytes
83     },
84     'resources': {
85         'number': 200,
86         'name': 255  # in characters
87     },
88     'template': {
89         'body': 460800,  # in bytes
90         'description': 1024  # in bytes
91     }
92 }
93 
94 
95 def load_resources(filename='/data/CloudSpecs/us-east-1.json'):
96     """Load resources"""
97 
98     filename = pkg_resources.resource_filename(
99         __name__,
100         filename
101     )
102 
103     data = json.load(open(filename))
104 
105     return data
106 
107 
108 RESOURCE_SPECS = {}
109 
110 
111 def merge_spec(source, destination):
112     """ Recursive merge spec dict """
113 
114     for key, value in source.items():
115         if isinstance(value, dict):
116             node = destination.setdefault(key, {})
117             merge_spec(value, node)
118         else:
119             destination[key] = value
120 
121     return destination
122 
123 
124 def set_specs(override_spec_data):
125     """ Override Resource Specs """
126 
127     excludes = []
128     includes = []
129 
130     # Extract the exclude list from the override file
131     if 'ExcludeResourceTypes' in override_spec_data:
132         excludes = override_spec_data.pop('ExcludeResourceTypes')
133     if 'IncludeResourceTypes' in override_spec_data:
134         includes = override_spec_data.pop('IncludeResourceTypes')
135 
136     for region, spec in RESOURCE_SPECS.items():
137 
138         # Merge override spec file into the AWS Resource specification
139         if override_spec_data:
140             RESOURCE_SPECS[region] = merge_spec(override_spec_data, spec)
141 
142         # Grab a list of all resources
143         all_resources = list(RESOURCE_SPECS[region]['ResourceTypes'].keys())[:]
144 
145         resources = []
146 
147         # Remove unsupported resource using includes
148         if includes:
149             for include in includes:
150                 regex = re.compile(include.replace('*', '(.*)') + '$')
151                 matches = [string for string in all_resources if re.match(regex, string)]
152 
153                 resources.extend(matches)
154         else:
155             resources = all_resources[:]
156 
157         # Remove unsupported resources using the excludes
158         if excludes:
159             for exclude in excludes:
160                 regex = re.compile(exclude.replace('*', '(.*)') + '$')
161                 matches = [string for string in resources if re.match(regex, string)]
162 
163                 for match in matches:
164                     resources.remove(match)
165 
166         # Remove unsupported resources
167         for resource in all_resources:
168             if resource not in resources:
169                 del RESOURCE_SPECS[region]['ResourceTypes'][resource]
170 
171 
172 def initialize_specs():
173     """ Reload Resource Specs """
174     for reg in REGIONS:
175         RESOURCE_SPECS[reg] = load_resources(filename=('/data/CloudSpecs/%s.json' % reg))
176 
177 
178 initialize_specs()
179 
180 
181 def load_plugins(directory):
182     """Load plugins"""
183     result = []
184     fh = None
185 
186     for root, _, filenames in os.walk(directory):
187         for filename in fnmatch.filter(filenames, '[A-Za-z]*.py'):
188             pluginname = filename.replace('.py', '')
189             try:
190                 fh, filename, desc = imp.find_module(pluginname, [root])
191                 mod = imp.load_module(pluginname, fh, filename, desc)
192                 obj = getattr(mod, pluginname)()
193                 result.append(obj)
194             finally:
195                 if fh:
196                     fh.close()
197     return result
198 
199 
200 def override_specs(override_spec_file):
201     """Override specs file"""
202     try:
203         filename = override_spec_file
204         custom_spec_data = json.load(open(filename))
205 
206         set_specs(custom_spec_data)
207     except IOError as e:
208         if e.errno == 2:
209             LOGGER.error('Override spec file not found: %s', filename)
210             sys.exit(1)
211         elif e.errno == 21:
212             LOGGER.error('Override spec file references a directory, not a file: %s', filename)
213             sys.exit(1)
214         elif e.errno == 13:
215             LOGGER.error('Permission denied when accessing override spec file: %s', filename)
216             sys.exit(1)
217     except (ValueError) as err:
218         LOGGER.error('Override spec file %s is malformed: %s', filename, err)
219         sys.exit(1)
220
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/cfnlint/helpers.py b/src/cfnlint/helpers.py
--- a/src/cfnlint/helpers.py
+++ b/src/cfnlint/helpers.py
@@ -21,6 +21,7 @@
 import imp
 import logging
 import re
+import inspect
 import pkg_resources
 
 
@@ -189,8 +190,12 @@
             try:
                 fh, filename, desc = imp.find_module(pluginname, [root])
                 mod = imp.load_module(pluginname, fh, filename, desc)
-                obj = getattr(mod, pluginname)()
-                result.append(obj)
+                for _, clazz in inspect.getmembers(mod, inspect.isclass):
+                    method_resolution = inspect.getmro(clazz)
+                    if [clz for clz in method_resolution[1:] if clz.__module__ == 'cfnlint' and clz.__name__ == 'CloudFormationLintRule']:
+                        # create and instance of subclasses of CloudFormationLintRule
+                        obj = clazz()
+                        result.append(obj)
             finally:
                 if fh:
                     fh.close()

{"golden_diff": "diff --git a/src/cfnlint/helpers.py b/src/cfnlint/helpers.py\n--- a/src/cfnlint/helpers.py\n+++ b/src/cfnlint/helpers.py\n@@ -21,6 +21,7 @@\n import imp\n import logging\n import re\n+import inspect\n import pkg_resources\n \n \n@@ -189,8 +190,12 @@\n             try:\n                 fh, filename, desc = imp.find_module(pluginname, [root])\n                 mod = imp.load_module(pluginname, fh, filename, desc)\n-                obj = getattr(mod, pluginname)()\n-                result.append(obj)\n+                for _, clazz in inspect.getmembers(mod, inspect.isclass):\n+                    method_resolution = inspect.getmro(clazz)\n+                    if [clz for clz in method_resolution[1:] if clz.__module__ == 'cfnlint' and clz.__name__ == 'CloudFormationLintRule']:\n+                        # create and instance of subclasses of CloudFormationLintRule\n+                        obj = clazz()\n+                        result.append(obj)\n             finally:\n                 if fh:\n                     fh.close()\n", "issue": "Adding new CloudFormationLintRule's requires the filename to be identical to the Class name\n*cfn-lint version: (`cfn-lint --version`)*\r\n0.0.9\r\n\r\n*Description of issue.*\r\n\r\nWhen creating your own rules, you have to add  CloudFormationLintRules, but the loader requires\r\nthe python filename to be identical to the Classname.\r\n\r\nIt would be nice if:\r\n- we can create files with names matching the python convention.\r\n- we could add multiple lint rules in a single module.\r\n\n", "before_files": [{"content": "\"\"\"\n  Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this\n  software and associated documentation files (the \"Software\"), to deal in the Software\n  without restriction, including without limitation the rights to use, copy, modify,\n  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n  permit persons to whom the Software is furnished to do so.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\nimport sys\nimport fnmatch\nimport json\nimport os\nimport imp\nimport logging\nimport re\nimport pkg_resources\n\n\nLOGGER = logging.getLogger(__name__)\n\nTAG_MAP = 'tag:yaml.org,2002:map'\nUNCONVERTED_SUFFIXES = ['Ref', 'Condition']\nFN_PREFIX = 'Fn::'\nCONDITION_FUNCTIONS = ['Fn::If']\nREGIONS = ['us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', 'ca-central-1',\n           'eu-central-1', 'eu-west-1', 'eu-west-2', 'ap-northeast-1',\n           'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-south-1',\n           'sa-east-1']\n\nREGEX_ALPHANUMERIC = re.compile('^[a-zA-Z0-9]*$')\nREGEX_CIDR = re.compile(r'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/([0-9]|[1-2][0-9]|3[0-2]))$')\nREGEX_IPV4 = re.compile(r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$')\nREGEX_IPV6 = re.compile(r'^(((?=.*(::))(?!.*\\3.+\\3))\\3?|[\\dA-F]{1,4}:)([\\dA-F]{1,4}(\\3|:\\b)|\\2){5}(([\\dA-F]{1,4}(\\3|:\\b|$)|\\2){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})\\Z', re.I | re.S)\n\nAVAILABILITY_ZONES = [\n    'us-east-1a', 'us-east-1b', 'us-east-1c', 'us-east-1d', 'us-east-1e', 'us-east-1f',\n    'us-east-2a' 'us-east-2b' 'us-east-2c',\n    'us-west-1a', 'us-west-1b', 'us-west-1c',\n    'us-west-2a', 'us-west-2b', 'us-west-2c',\n    'ca-central-1a', 'ca-central-1b',\n    'sa-east-1a', 'sa-east-1b', 'sa-east-1c',\n    'eu-west-1a', 'eu-west-1b', 'eu-west-1c',\n    'eu-west-2a', 'eu-west-2b', 'eu-west-2c',\n    'eu-west-3a', 'eu-west-3b', 'eu-west-3c',\n    'eu-central-1a', 'eu-central-1b', 'eu-central-1c',\n    'eu-central-2a', 'eu-central-2b', 'eu-central-2c',\n    'ap-northeast-1a', 'ap-northeast-1b', 'ap-northeast-1c', 'ap-northeast-1d',\n    'ap-northeast-2a', 'ap-northeast-2b',\n    'ap-northeast-3a',\n    'ap-southeast-1a', 'ap-southeast-1b', 'ap-southeast-1c',\n    'ap-southeast-2a', 'ap-southeast-2b', 'ap-southeast-2c',\n    'ap-south-1a', 'ap-south-1b',\n    'cn-north-1a', 'cn-north-1b',\n]\n\nFUNCTIONS = [\n    'Fn::Base64', 'Fn::GetAtt', 'Fn::GetAZs', 'Fn::ImportValue',\n    'Fn::Join', 'Fn::Split', 'Fn::FindInMap', 'Fn::Select', 'Ref',\n    'Fn::If', 'Fn::Contains', 'Fn::Sub', 'Fn::Cidr']\n\nLIMITS = {\n    'mappings': {\n        'number': 100,\n        'attributes': 64,\n        'name': 255  # in characters\n    },\n    'outputs': {\n        'number': 60,\n        'name': 255  # in characters\n    },\n    'parameters': {\n        'number': 60,\n        'name': 255,  # in characters\n        'value': 4096  # in bytes\n    },\n    'resources': {\n        'number': 200,\n        'name': 255  # in characters\n    },\n    'template': {\n        'body': 460800,  # in bytes\n        'description': 1024  # in bytes\n    }\n}\n\n\ndef load_resources(filename='/data/CloudSpecs/us-east-1.json'):\n    \"\"\"Load resources\"\"\"\n\n    filename = pkg_resources.resource_filename(\n        __name__,\n        filename\n    )\n\n    data = json.load(open(filename))\n\n    return data\n\n\nRESOURCE_SPECS = {}\n\n\ndef merge_spec(source, destination):\n    \"\"\" Recursive merge spec dict \"\"\"\n\n    for key, value in source.items():\n        if isinstance(value, dict):\n            node = destination.setdefault(key, {})\n            merge_spec(value, node)\n        else:\n            destination[key] = value\n\n    return destination\n\n\ndef set_specs(override_spec_data):\n    \"\"\" Override Resource Specs \"\"\"\n\n    excludes = []\n    includes = []\n\n    # Extract the exclude list from the override file\n    if 'ExcludeResourceTypes' in override_spec_data:\n        excludes = override_spec_data.pop('ExcludeResourceTypes')\n    if 'IncludeResourceTypes' in override_spec_data:\n        includes = override_spec_data.pop('IncludeResourceTypes')\n\n    for region, spec in RESOURCE_SPECS.items():\n\n        # Merge override spec file into the AWS Resource specification\n        if override_spec_data:\n            RESOURCE_SPECS[region] = merge_spec(override_spec_data, spec)\n\n        # Grab a list of all resources\n        all_resources = list(RESOURCE_SPECS[region]['ResourceTypes'].keys())[:]\n\n        resources = []\n\n        # Remove unsupported resource using includes\n        if includes:\n            for include in includes:\n                regex = re.compile(include.replace('*', '(.*)') + '$')\n                matches = [string for string in all_resources if re.match(regex, string)]\n\n                resources.extend(matches)\n        else:\n            resources = all_resources[:]\n\n        # Remove unsupported resources using the excludes\n        if excludes:\n            for exclude in excludes:\n                regex = re.compile(exclude.replace('*', '(.*)') + '$')\n                matches = [string for string in resources if re.match(regex, string)]\n\n                for match in matches:\n                    resources.remove(match)\n\n        # Remove unsupported resources\n        for resource in all_resources:\n            if resource not in resources:\n                del RESOURCE_SPECS[region]['ResourceTypes'][resource]\n\n\ndef initialize_specs():\n    \"\"\" Reload Resource Specs \"\"\"\n    for reg in REGIONS:\n        RESOURCE_SPECS[reg] = load_resources(filename=('/data/CloudSpecs/%s.json' % reg))\n\n\ninitialize_specs()\n\n\ndef load_plugins(directory):\n    \"\"\"Load plugins\"\"\"\n    result = []\n    fh = None\n\n    for root, _, filenames in os.walk(directory):\n        for filename in fnmatch.filter(filenames, '[A-Za-z]*.py'):\n            pluginname = filename.replace('.py', '')\n            try:\n                fh, filename, desc = imp.find_module(pluginname, [root])\n                mod = imp.load_module(pluginname, fh, filename, desc)\n                obj = getattr(mod, pluginname)()\n                result.append(obj)\n            finally:\n                if fh:\n                    fh.close()\n    return result\n\n\ndef override_specs(override_spec_file):\n    \"\"\"Override specs file\"\"\"\n    try:\n        filename = override_spec_file\n        custom_spec_data = json.load(open(filename))\n\n        set_specs(custom_spec_data)\n    except IOError as e:\n        if e.errno == 2:\n            LOGGER.error('Override spec file not found: %s', filename)\n            sys.exit(1)\n        elif e.errno == 21:\n            LOGGER.error('Override spec file references a directory, not a file: %s', filename)\n            sys.exit(1)\n        elif e.errno == 13:\n            LOGGER.error('Permission denied when accessing override spec file: %s', filename)\n            sys.exit(1)\n    except (ValueError) as err:\n        LOGGER.error('Override spec file %s is malformed: %s', filename, err)\n        sys.exit(1)\n", "path": "src/cfnlint/helpers.py"}], "after_files": [{"content": "\"\"\"\n  Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this\n  software and associated documentation files (the \"Software\"), to deal in the Software\n  without restriction, including without limitation the rights to use, copy, modify,\n  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n  permit persons to whom the Software is furnished to do so.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\nimport sys\nimport fnmatch\nimport json\nimport os\nimport imp\nimport logging\nimport re\nimport inspect\nimport pkg_resources\n\n\nLOGGER = logging.getLogger(__name__)\n\nTAG_MAP = 'tag:yaml.org,2002:map'\nUNCONVERTED_SUFFIXES = ['Ref', 'Condition']\nFN_PREFIX = 'Fn::'\nCONDITION_FUNCTIONS = ['Fn::If']\nREGIONS = ['us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', 'ca-central-1',\n           'eu-central-1', 'eu-west-1', 'eu-west-2', 'ap-northeast-1',\n           'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-south-1',\n           'sa-east-1']\n\nREGEX_ALPHANUMERIC = re.compile('^[a-zA-Z0-9]*$')\nREGEX_CIDR = re.compile(r'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/([0-9]|[1-2][0-9]|3[0-2]))$')\nREGEX_IPV4 = re.compile(r'^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$')\nREGEX_IPV6 = re.compile(r'^(((?=.*(::))(?!.*\\3.+\\3))\\3?|[\\dA-F]{1,4}:)([\\dA-F]{1,4}(\\3|:\\b)|\\2){5}(([\\dA-F]{1,4}(\\3|:\\b|$)|\\2){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})\\Z', re.I | re.S)\n\nAVAILABILITY_ZONES = [\n    'us-east-1a', 'us-east-1b', 'us-east-1c', 'us-east-1d', 'us-east-1e', 'us-east-1f',\n    'us-east-2a' 'us-east-2b' 'us-east-2c',\n    'us-west-1a', 'us-west-1b', 'us-west-1c',\n    'us-west-2a', 'us-west-2b', 'us-west-2c',\n    'ca-central-1a', 'ca-central-1b',\n    'sa-east-1a', 'sa-east-1b', 'sa-east-1c',\n    'eu-west-1a', 'eu-west-1b', 'eu-west-1c',\n    'eu-west-2a', 'eu-west-2b', 'eu-west-2c',\n    'eu-west-3a', 'eu-west-3b', 'eu-west-3c',\n    'eu-central-1a', 'eu-central-1b', 'eu-central-1c',\n    'eu-central-2a', 'eu-central-2b', 'eu-central-2c',\n    'ap-northeast-1a', 'ap-northeast-1b', 'ap-northeast-1c', 'ap-northeast-1d',\n    'ap-northeast-2a', 'ap-northeast-2b',\n    'ap-northeast-3a',\n    'ap-southeast-1a', 'ap-southeast-1b', 'ap-southeast-1c',\n    'ap-southeast-2a', 'ap-southeast-2b', 'ap-southeast-2c',\n    'ap-south-1a', 'ap-south-1b',\n    'cn-north-1a', 'cn-north-1b',\n]\n\nFUNCTIONS = [\n    'Fn::Base64', 'Fn::GetAtt', 'Fn::GetAZs', 'Fn::ImportValue',\n    'Fn::Join', 'Fn::Split', 'Fn::FindInMap', 'Fn::Select', 'Ref',\n    'Fn::If', 'Fn::Contains', 'Fn::Sub', 'Fn::Cidr']\n\nLIMITS = {\n    'mappings': {\n        'number': 100,\n        'attributes': 64,\n        'name': 255  # in characters\n    },\n    'outputs': {\n        'number': 60,\n        'name': 255  # in characters\n    },\n    'parameters': {\n        'number': 60,\n        'name': 255,  # in characters\n        'value': 4096  # in bytes\n    },\n    'resources': {\n        'number': 200,\n        'name': 255  # in characters\n    },\n    'template': {\n        'body': 460800,  # in bytes\n        'description': 1024  # in bytes\n    }\n}\n\n\ndef load_resources(filename='/data/CloudSpecs/us-east-1.json'):\n    \"\"\"Load resources\"\"\"\n\n    filename = pkg_resources.resource_filename(\n        __name__,\n        filename\n    )\n\n    data = json.load(open(filename))\n\n    return data\n\n\nRESOURCE_SPECS = {}\n\n\ndef merge_spec(source, destination):\n    \"\"\" Recursive merge spec dict \"\"\"\n\n    for key, value in source.items():\n        if isinstance(value, dict):\n            node = destination.setdefault(key, {})\n            merge_spec(value, node)\n        else:\n            destination[key] = value\n\n    return destination\n\n\ndef set_specs(override_spec_data):\n    \"\"\" Override Resource Specs \"\"\"\n\n    excludes = []\n    includes = []\n\n    # Extract the exclude list from the override file\n    if 'ExcludeResourceTypes' in override_spec_data:\n        excludes = override_spec_data.pop('ExcludeResourceTypes')\n    if 'IncludeResourceTypes' in override_spec_data:\n        includes = override_spec_data.pop('IncludeResourceTypes')\n\n    for region, spec in RESOURCE_SPECS.items():\n\n        # Merge override spec file into the AWS Resource specification\n        if override_spec_data:\n            RESOURCE_SPECS[region] = merge_spec(override_spec_data, spec)\n\n        # Grab a list of all resources\n        all_resources = list(RESOURCE_SPECS[region]['ResourceTypes'].keys())[:]\n\n        resources = []\n\n        # Remove unsupported resource using includes\n        if includes:\n            for include in includes:\n                regex = re.compile(include.replace('*', '(.*)') + '$')\n                matches = [string for string in all_resources if re.match(regex, string)]\n\n                resources.extend(matches)\n        else:\n            resources = all_resources[:]\n\n        # Remove unsupported resources using the excludes\n        if excludes:\n            for exclude in excludes:\n                regex = re.compile(exclude.replace('*', '(.*)') + '$')\n                matches = [string for string in resources if re.match(regex, string)]\n\n                for match in matches:\n                    resources.remove(match)\n\n        # Remove unsupported resources\n        for resource in all_resources:\n            if resource not in resources:\n                del RESOURCE_SPECS[region]['ResourceTypes'][resource]\n\n\ndef initialize_specs():\n    \"\"\" Reload Resource Specs \"\"\"\n    for reg in REGIONS:\n        RESOURCE_SPECS[reg] = load_resources(filename=('/data/CloudSpecs/%s.json' % reg))\n\n\ninitialize_specs()\n\n\ndef load_plugins(directory):\n    \"\"\"Load plugins\"\"\"\n    result = []\n    fh = None\n\n    for root, _, filenames in os.walk(directory):\n        for filename in fnmatch.filter(filenames, '[A-Za-z]*.py'):\n            pluginname = filename.replace('.py', '')\n            try:\n                fh, filename, desc = imp.find_module(pluginname, [root])\n                mod = imp.load_module(pluginname, fh, filename, desc)\n                for _, clazz in inspect.getmembers(mod, inspect.isclass):\n                    method_resolution = inspect.getmro(clazz)\n                    if [clz for clz in method_resolution[1:] if clz.__module__ == 'cfnlint' and clz.__name__ == 'CloudFormationLintRule']:\n                        # create and instance of subclasses of CloudFormationLintRule\n                        obj = clazz()\n                        result.append(obj)\n            finally:\n                if fh:\n                    fh.close()\n    return result\n\n\ndef override_specs(override_spec_file):\n    \"\"\"Override specs file\"\"\"\n    try:\n        filename = override_spec_file\n        custom_spec_data = json.load(open(filename))\n\n        set_specs(custom_spec_data)\n    except IOError as e:\n        if e.errno == 2:\n            LOGGER.error('Override spec file not found: %s', filename)\n            sys.exit(1)\n        elif e.errno == 21:\n            LOGGER.error('Override spec file references a directory, not a file: %s', filename)\n            sys.exit(1)\n        elif e.errno == 13:\n            LOGGER.error('Permission denied when accessing override spec file: %s', filename)\n            sys.exit(1)\n    except (ValueError) as err:\n        LOGGER.error('Override spec file %s is malformed: %s', filename, err)\n        sys.exit(1)\n", "path": "src/cfnlint/helpers.py"}]}