prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Update regex pattern for the envvar construtor
  You can use envvars on your configuration, but they need to follow this pattern `$ENVVARNAME` the regex pattern should be updated to allow users to use either `$ENVVARNAME` or `${ENVVARNAME}`. 
  
  While we are at it we should allow users to use other characters as well like `_` or `-`. 
  
  This change needs to be done on [opsdroid.loader.load_config_file](https://github.com/opsdroid/opsdroid/blob/674013037eab826640174407a73f8fed1a29b290/opsdroid/loader.py#L347)
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `opsdroid/const.py`
  Content:
  ```
  1 """Constants used by OpsDroid."""
  2 import os
  3 from appdirs import user_log_dir, user_config_dir, user_data_dir
  4 import opsdroid
  5 from opsdroid import __version__  # noqa # pylint: disable=unused-import
  6 
  7 NAME = "opsdroid"
  8 MODULE_ROOT = os.path.dirname(os.path.abspath(opsdroid.__file__))
  9 DEFAULT_GIT_URL = "https://github.com/opsdroid/"
  10 MODULES_DIRECTORY = "opsdroid-modules"
  11 DEFAULT_ROOT_PATH = user_data_dir(NAME)
  12 DEFAULT_LOG_FILENAME = os.path.join(user_log_dir(NAME, appauthor=False), "output.log")
  13 DEFAULT_MODULES_PATH = user_data_dir(NAME, MODULES_DIRECTORY)
  14 DEFAULT_MODULE_DEPS_PATH = os.path.join(
  15     user_data_dir(NAME, MODULES_DIRECTORY), "site-packages"
  16 )
  17 DEFAULT_CONFIG_PATH = os.path.join(
  18     user_config_dir(NAME, appauthor=False), "configuration.yaml"
  19 )
  20 DEFAULT_MODULE_BRANCH = "master"
  21 DEFAULT_LANGUAGE = "en"
  22 LOCALE_DIR = os.path.join(MODULE_ROOT, "locale")
  23 EXAMPLE_CONFIG_FILE = os.path.join(
  24     os.path.dirname(os.path.abspath(__file__)),
  25     "configuration/example_configuration.yaml",
  26 )
  27 REGEX_PARSE_SCORE_FACTOR = 0.6
  28 
  29 RASANLU_DEFAULT_URL = "http://localhost:5000"
  30 RASANLU_DEFAULT_PROJECT = "opsdroid"
  31 
  32 LUISAI_DEFAULT_URL = "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/"
  33 
  34 DIALOGFLOW_API_ENDPOINT = "https://api.dialogflow.com/v1/query"
  35 DIALOGFLOW_API_VERSION = "20150910"
  36 
  37 WITAI_DEFAULT_VERSION = "20170307"
  38 WITAI_API_ENDPOINT = "https://api.wit.ai/message?"
  39 
  40 SAPCAI_API_ENDPOINT = "https://api.cai.tools.sap/v2/request"
  41 
  42 WATSON_API_ENDPOINT = "https://{gateway}.watsonplatform.net/assistant/api"
  43 WATSON_API_VERSION = "2019-02-28"
  44
  ```
  
  Path: `opsdroid/configuration/__init__.py`
  Content:
  ```
  1 """Load configuration from yaml file."""
  2 
  3 import os
  4 import shutil
  5 import sys
  6 import re
  7 import logging
  8 import yaml
  9 
  10 from opsdroid.const import DEFAULT_CONFIG_PATH, EXAMPLE_CONFIG_FILE
  11 from opsdroid.configuration.validation import validate_configuration, BASE_SCHEMA
  12 from opsdroid.helper import update_pre_0_17_config_format
  13 
  14 
  15 _LOGGER = logging.getLogger(__name__)
  16 
  17 
  18 def create_default_config(config_path):
  19     """Create a default config file based on the example config file.
  20 
  21     If we can't find any configuration.yaml, we will pull the whole
  22     example_configuration.yaml and use this file as the configuration.
  23 
  24     Args:
  25         config_path: String containing the path to configuration.yaml
  26             default install location
  27 
  28     Returns:
  29         str: path to configuration.yaml default install location
  30 
  31     """
  32     _LOGGER.info("Creating %s.", config_path)
  33     config_dir, _ = os.path.split(config_path)
  34     if not os.path.isdir(config_dir):
  35         os.makedirs(config_dir)
  36     shutil.copyfile(EXAMPLE_CONFIG_FILE, config_path)
  37     return config_path
  38 
  39 
  40 def get_config_path(config_paths):
  41     """Get the path to configuration.yaml.
  42 
  43     Opsdroid configuration.yaml can be located in different paths.
  44     With this function, we will go through all of the possible paths and
  45     return the correct path.
  46 
  47     If we don't have any configuration.yaml we will just create one using
  48     the example configuration file.
  49 
  50     Args:
  51         config_paths: List containing all the possible config paths.
  52 
  53     Returns:
  54         str: Path to the configuration file.
  55 
  56     """
  57     config_path = ""
  58     for possible_path in config_paths:
  59         if not os.path.isfile(possible_path):
  60             _LOGGER.debug(_("Config file %s not found."), possible_path)
  61         else:
  62             config_path = possible_path
  63             break
  64 
  65     if not config_path:
  66         _LOGGER.info(
  67             _("No configuration files found. Creating %s"), DEFAULT_CONFIG_PATH
  68         )
  69         config_path = create_default_config(DEFAULT_CONFIG_PATH)
  70 
  71     return config_path
  72 
  73 
  74 env_var_pattern = re.compile(r"^\$([A-Z_]*)$")
  75 
  76 
  77 def envvar_constructor(loader, node):
  78     """Yaml parser for env vars."""
  79     value = loader.construct_scalar(node)
  80     [env_var] = env_var_pattern.match(value).groups()
  81     return os.environ[env_var]
  82 
  83 
  84 def load_config_file(config_paths):
  85     """Load a yaml config file from path.
  86 
  87     We get a path for the configuration file and then use the yaml
  88     library to load this file - the configuration will be shown as a
  89     dict.  Here we also add constructors to our yaml loader and handle
  90     different exceptions that could be raised when trying to load or
  91     validate the file.
  92 
  93     Args:
  94         config_paths: List of paths to configuration.yaml files
  95 
  96     Returns:
  97         dict: Dict containing config fields
  98 
  99     """
  100 
  101     config_path = get_config_path(config_paths)
  102 
  103     yaml.SafeLoader.add_implicit_resolver("!envvar", env_var_pattern, first="$")
  104     yaml.SafeLoader.add_constructor("!envvar", envvar_constructor)
  105 
  106     try:
  107         with open(config_path, "r") as stream:
  108             _LOGGER.info(_("Loaded config from %s."), config_path)
  109 
  110             data = yaml.load(stream, Loader=yaml.SafeLoader)
  111             configuration = update_pre_0_17_config_format(data)
  112             validate_configuration(configuration, BASE_SCHEMA)
  113 
  114             return configuration
  115 
  116     except yaml.YAMLError as error:
  117         _LOGGER.critical(error)
  118         sys.exit(1)
  119 
  120     except FileNotFoundError as error:
  121         _LOGGER.critical(error)
  122         sys.exit(1)
  123
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/opsdroid/configuration/__init__.py b/opsdroid/configuration/__init__.py
  --- a/opsdroid/configuration/__init__.py
  +++ b/opsdroid/configuration/__init__.py
  @@ -7,7 +7,7 @@
   import logging
   import yaml
   
  -from opsdroid.const import DEFAULT_CONFIG_PATH, EXAMPLE_CONFIG_FILE
  +from opsdroid.const import DEFAULT_CONFIG_PATH, ENV_VAR_REGEX, EXAMPLE_CONFIG_FILE
   from opsdroid.configuration.validation import validate_configuration, BASE_SCHEMA
   from opsdroid.helper import update_pre_0_17_config_format
   
  @@ -71,7 +71,7 @@
       return config_path
   
   
  -env_var_pattern = re.compile(r"^\$([A-Z_]*)$")
  +env_var_pattern = re.compile(ENV_VAR_REGEX)
   
   
   def envvar_constructor(loader, node):
  diff --git a/opsdroid/const.py b/opsdroid/const.py
  --- a/opsdroid/const.py
  +++ b/opsdroid/const.py
  @@ -41,3 +41,4 @@
   
   WATSON_API_ENDPOINT = "https://{gateway}.watsonplatform.net/assistant/api"
   WATSON_API_VERSION = "2019-02-28"
  +ENV_VAR_REGEX = r"^\"?\${?(?=\_?[A-Z])([A-Z-_]+)}?\"?$"
  
verification_info: {"golden_diff": "diff --git a/opsdroid/configuration/__init__.py b/opsdroid/configuration/__init__.py\n--- a/opsdroid/configuration/__init__.py\n+++ b/opsdroid/configuration/__init__.py\n@@ -7,7 +7,7 @@\n import logging\n import yaml\n \n-from opsdroid.const import DEFAULT_CONFIG_PATH, EXAMPLE_CONFIG_FILE\n+from opsdroid.const import DEFAULT_CONFIG_PATH, ENV_VAR_REGEX, EXAMPLE_CONFIG_FILE\n from opsdroid.configuration.validation import validate_configuration, BASE_SCHEMA\n from opsdroid.helper import update_pre_0_17_config_format\n \n@@ -71,7 +71,7 @@\n     return config_path\n \n \n-env_var_pattern = re.compile(r\"^\\$([A-Z_]*)$\")\n+env_var_pattern = re.compile(ENV_VAR_REGEX)\n \n \n def envvar_constructor(loader, node):\ndiff --git a/opsdroid/const.py b/opsdroid/const.py\n--- a/opsdroid/const.py\n+++ b/opsdroid/const.py\n@@ -41,3 +41,4 @@\n \n WATSON_API_ENDPOINT = \"https://{gateway}.watsonplatform.net/assistant/api\"\n WATSON_API_VERSION = \"2019-02-28\"\n+ENV_VAR_REGEX = r\"^\\\"?\\${?(?=\\_?[A-Z])([A-Z-_]+)}?\\\"?$\"\n", "issue": "Update regex pattern for the envvar construtor\nYou can use envvars on your configuration, but they need to follow this pattern `$ENVVARNAME` the regex pattern should be updated to allow users to use either `$ENVVARNAME` or `${ENVVARNAME}`. \r\n\r\nWhile we are at it we should allow users to use other characters as well like `_` or `-`. \r\n\r\nThis change needs to be done on [opsdroid.loader.load_config_file](https://github.com/opsdroid/opsdroid/blob/674013037eab826640174407a73f8fed1a29b290/opsdroid/loader.py#L347)\n", "before_files": [{"content": "\"\"\"Constants used by OpsDroid.\"\"\"\nimport os\nfrom appdirs import user_log_dir, user_config_dir, user_data_dir\nimport opsdroid\nfrom opsdroid import __version__  # noqa # pylint: disable=unused-import\n\nNAME = \"opsdroid\"\nMODULE_ROOT = os.path.dirname(os.path.abspath(opsdroid.__file__))\nDEFAULT_GIT_URL = \"https://github.com/opsdroid/\"\nMODULES_DIRECTORY = \"opsdroid-modules\"\nDEFAULT_ROOT_PATH = user_data_dir(NAME)\nDEFAULT_LOG_FILENAME = os.path.join(user_log_dir(NAME, appauthor=False), \"output.log\")\nDEFAULT_MODULES_PATH = user_data_dir(NAME, MODULES_DIRECTORY)\nDEFAULT_MODULE_DEPS_PATH = os.path.join(\n    user_data_dir(NAME, MODULES_DIRECTORY), \"site-packages\"\n)\nDEFAULT_CONFIG_PATH = os.path.join(\n    user_config_dir(NAME, appauthor=False), \"configuration.yaml\"\n)\nDEFAULT_MODULE_BRANCH = \"master\"\nDEFAULT_LANGUAGE = \"en\"\nLOCALE_DIR = os.path.join(MODULE_ROOT, \"locale\")\nEXAMPLE_CONFIG_FILE = os.path.join(\n    os.path.dirname(os.path.abspath(__file__)),\n    \"configuration/example_configuration.yaml\",\n)\nREGEX_PARSE_SCORE_FACTOR = 0.6\n\nRASANLU_DEFAULT_URL = \"http://localhost:5000\"\nRASANLU_DEFAULT_PROJECT = \"opsdroid\"\n\nLUISAI_DEFAULT_URL = \"https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/\"\n\nDIALOGFLOW_API_ENDPOINT = \"https://api.dialogflow.com/v1/query\"\nDIALOGFLOW_API_VERSION = \"20150910\"\n\nWITAI_DEFAULT_VERSION = \"20170307\"\nWITAI_API_ENDPOINT = \"https://api.wit.ai/message?\"\n\nSAPCAI_API_ENDPOINT = \"https://api.cai.tools.sap/v2/request\"\n\nWATSON_API_ENDPOINT = \"https://{gateway}.watsonplatform.net/assistant/api\"\nWATSON_API_VERSION = \"2019-02-28\"\n", "path": "opsdroid/const.py"}, {"content": "\"\"\"Load configuration from yaml file.\"\"\"\n\nimport os\nimport shutil\nimport sys\nimport re\nimport logging\nimport yaml\n\nfrom opsdroid.const import DEFAULT_CONFIG_PATH, EXAMPLE_CONFIG_FILE\nfrom opsdroid.configuration.validation import validate_configuration, BASE_SCHEMA\nfrom opsdroid.helper import update_pre_0_17_config_format\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\ndef create_default_config(config_path):\n    \"\"\"Create a default config file based on the example config file.\n\n    If we can't find any configuration.yaml, we will pull the whole\n    example_configuration.yaml and use this file as the configuration.\n\n    Args:\n        config_path: String containing the path to configuration.yaml\n            default install location\n\n    Returns:\n        str: path to configuration.yaml default install location\n\n    \"\"\"\n    _LOGGER.info(\"Creating %s.\", config_path)\n    config_dir, _ = os.path.split(config_path)\n    if not os.path.isdir(config_dir):\n        os.makedirs(config_dir)\n    shutil.copyfile(EXAMPLE_CONFIG_FILE, config_path)\n    return config_path\n\n\ndef get_config_path(config_paths):\n    \"\"\"Get the path to configuration.yaml.\n\n    Opsdroid configuration.yaml can be located in different paths.\n    With this function, we will go through all of the possible paths and\n    return the correct path.\n\n    If we don't have any configuration.yaml we will just create one using\n    the example configuration file.\n\n    Args:\n        config_paths: List containing all the possible config paths.\n\n    Returns:\n        str: Path to the configuration file.\n\n    \"\"\"\n    config_path = \"\"\n    for possible_path in config_paths:\n        if not os.path.isfile(possible_path):\n            _LOGGER.debug(_(\"Config file %s not found.\"), possible_path)\n        else:\n            config_path = possible_path\n            break\n\n    if not config_path:\n        _LOGGER.info(\n            _(\"No configuration files found. Creating %s\"), DEFAULT_CONFIG_PATH\n        )\n        config_path = create_default_config(DEFAULT_CONFIG_PATH)\n\n    return config_path\n\n\nenv_var_pattern = re.compile(r\"^\\$([A-Z_]*)$\")\n\n\ndef envvar_constructor(loader, node):\n    \"\"\"Yaml parser for env vars.\"\"\"\n    value = loader.construct_scalar(node)\n    [env_var] = env_var_pattern.match(value).groups()\n    return os.environ[env_var]\n\n\ndef load_config_file(config_paths):\n    \"\"\"Load a yaml config file from path.\n\n    We get a path for the configuration file and then use the yaml\n    library to load this file - the configuration will be shown as a\n    dict.  Here we also add constructors to our yaml loader and handle\n    different exceptions that could be raised when trying to load or\n    validate the file.\n\n    Args:\n        config_paths: List of paths to configuration.yaml files\n\n    Returns:\n        dict: Dict containing config fields\n\n    \"\"\"\n\n    config_path = get_config_path(config_paths)\n\n    yaml.SafeLoader.add_implicit_resolver(\"!envvar\", env_var_pattern, first=\"$\")\n    yaml.SafeLoader.add_constructor(\"!envvar\", envvar_constructor)\n\n    try:\n        with open(config_path, \"r\") as stream:\n            _LOGGER.info(_(\"Loaded config from %s.\"), config_path)\n\n            data = yaml.load(stream, Loader=yaml.SafeLoader)\n            configuration = update_pre_0_17_config_format(data)\n            validate_configuration(configuration, BASE_SCHEMA)\n\n            return configuration\n\n    except yaml.YAMLError as error:\n        _LOGGER.critical(error)\n        sys.exit(1)\n\n    except FileNotFoundError as error:\n        _LOGGER.critical(error)\n        sys.exit(1)\n", "path": "opsdroid/configuration/__init__.py"}], "after_files": [{"content": "\"\"\"Constants used by OpsDroid.\"\"\"\nimport os\nfrom appdirs import user_log_dir, user_config_dir, user_data_dir\nimport opsdroid\nfrom opsdroid import __version__  # noqa # pylint: disable=unused-import\n\nNAME = \"opsdroid\"\nMODULE_ROOT = os.path.dirname(os.path.abspath(opsdroid.__file__))\nDEFAULT_GIT_URL = \"https://github.com/opsdroid/\"\nMODULES_DIRECTORY = \"opsdroid-modules\"\nDEFAULT_ROOT_PATH = user_data_dir(NAME)\nDEFAULT_LOG_FILENAME = os.path.join(user_log_dir(NAME, appauthor=False), \"output.log\")\nDEFAULT_MODULES_PATH = user_data_dir(NAME, MODULES_DIRECTORY)\nDEFAULT_MODULE_DEPS_PATH = os.path.join(\n    user_data_dir(NAME, MODULES_DIRECTORY), \"site-packages\"\n)\nDEFAULT_CONFIG_PATH = os.path.join(\n    user_config_dir(NAME, appauthor=False), \"configuration.yaml\"\n)\nDEFAULT_MODULE_BRANCH = \"master\"\nDEFAULT_LANGUAGE = \"en\"\nLOCALE_DIR = os.path.join(MODULE_ROOT, \"locale\")\nEXAMPLE_CONFIG_FILE = os.path.join(\n    os.path.dirname(os.path.abspath(__file__)),\n    \"configuration/example_configuration.yaml\",\n)\nREGEX_PARSE_SCORE_FACTOR = 0.6\n\nRASANLU_DEFAULT_URL = \"http://localhost:5000\"\nRASANLU_DEFAULT_PROJECT = \"opsdroid\"\n\nLUISAI_DEFAULT_URL = \"https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/\"\n\nDIALOGFLOW_API_ENDPOINT = \"https://api.dialogflow.com/v1/query\"\nDIALOGFLOW_API_VERSION = \"20150910\"\n\nWITAI_DEFAULT_VERSION = \"20170307\"\nWITAI_API_ENDPOINT = \"https://api.wit.ai/message?\"\n\nSAPCAI_API_ENDPOINT = \"https://api.cai.tools.sap/v2/request\"\n\nWATSON_API_ENDPOINT = \"https://{gateway}.watsonplatform.net/assistant/api\"\nWATSON_API_VERSION = \"2019-02-28\"\nENV_VAR_REGEX = r\"^\\\"?\\${?(?=\\_?[A-Z])([A-Z-_]+)}?\\\"?$\"\n", "path": "opsdroid/const.py"}, {"content": "\"\"\"Load configuration from yaml file.\"\"\"\n\nimport os\nimport shutil\nimport sys\nimport re\nimport logging\nimport yaml\n\nfrom opsdroid.const import DEFAULT_CONFIG_PATH, ENV_VAR_REGEX, EXAMPLE_CONFIG_FILE\nfrom opsdroid.configuration.validation import validate_configuration, BASE_SCHEMA\nfrom opsdroid.helper import update_pre_0_17_config_format\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\ndef create_default_config(config_path):\n    \"\"\"Create a default config file based on the example config file.\n\n    If we can't find any configuration.yaml, we will pull the whole\n    example_configuration.yaml and use this file as the configuration.\n\n    Args:\n        config_path: String containing the path to configuration.yaml\n            default install location\n\n    Returns:\n        str: path to configuration.yaml default install location\n\n    \"\"\"\n    _LOGGER.info(\"Creating %s.\", config_path)\n    config_dir, _ = os.path.split(config_path)\n    if not os.path.isdir(config_dir):\n        os.makedirs(config_dir)\n    shutil.copyfile(EXAMPLE_CONFIG_FILE, config_path)\n    return config_path\n\n\ndef get_config_path(config_paths):\n    \"\"\"Get the path to configuration.yaml.\n\n    Opsdroid configuration.yaml can be located in different paths.\n    With this function, we will go through all of the possible paths and\n    return the correct path.\n\n    If we don't have any configuration.yaml we will just create one using\n    the example configuration file.\n\n    Args:\n        config_paths: List containing all the possible config paths.\n\n    Returns:\n        str: Path to the configuration file.\n\n    \"\"\"\n    config_path = \"\"\n    for possible_path in config_paths:\n        if not os.path.isfile(possible_path):\n            _LOGGER.debug(_(\"Config file %s not found.\"), possible_path)\n        else:\n            config_path = possible_path\n            break\n\n    if not config_path:\n        _LOGGER.info(\n            _(\"No configuration files found. Creating %s\"), DEFAULT_CONFIG_PATH\n        )\n        config_path = create_default_config(DEFAULT_CONFIG_PATH)\n\n    return config_path\n\n\nenv_var_pattern = re.compile(ENV_VAR_REGEX)\n\n\ndef envvar_constructor(loader, node):\n    \"\"\"Yaml parser for env vars.\"\"\"\n    value = loader.construct_scalar(node)\n    [env_var] = env_var_pattern.match(value).groups()\n    return os.environ[env_var]\n\n\ndef load_config_file(config_paths):\n    \"\"\"Load a yaml config file from path.\n\n    We get a path for the configuration file and then use the yaml\n    library to load this file - the configuration will be shown as a\n    dict.  Here we also add constructors to our yaml loader and handle\n    different exceptions that could be raised when trying to load or\n    validate the file.\n\n    Args:\n        config_paths: List of paths to configuration.yaml files\n\n    Returns:\n        dict: Dict containing config fields\n\n    \"\"\"\n\n    config_path = get_config_path(config_paths)\n\n    yaml.SafeLoader.add_implicit_resolver(\"!envvar\", env_var_pattern, first=\"$\")\n    yaml.SafeLoader.add_constructor(\"!envvar\", envvar_constructor)\n\n    try:\n        with open(config_path, \"r\") as stream:\n            _LOGGER.info(_(\"Loaded config from %s.\"), config_path)\n\n            data = yaml.load(stream, Loader=yaml.SafeLoader)\n            configuration = update_pre_0_17_config_format(data)\n            validate_configuration(configuration, BASE_SCHEMA)\n\n            return configuration\n\n    except yaml.YAMLError as error:\n        _LOGGER.critical(error)\n        sys.exit(1)\n\n    except FileNotFoundError as error:\n        _LOGGER.critical(error)\n        sys.exit(1)\n", "path": "opsdroid/configuration/__init__.py"}]}