We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Error if parsers is empty
# Description
If you leave the `parsers` section of the config as an empty dictionary an error is thrown and startup fails.

## Steps to Reproduce
Uncomment the parsers section of the config but with no actual parsers

```
parsers:

# nothing else
```

## Expected Functionality
The application  should start with the default parsers only. The same as if `parsers:` is not in the config.

## Experienced Functionality

```
DEBUG opsdroid.core: Parsing input: hi
DEBUG opsdroid.core: Processing parsers...
ERROR aiohttp.server: Error handling request
Traceback (most recent call last):
  File "/Users/jacob/.pyenv/versions/3.5.4/lib/python3.5/site-packages/aiohttp/web_protocol.py", line 416, in start
    resp = yield from self._request_handler(request)
  File "/Users/jacob/.pyenv/versions/3.5.4/lib/python3.5/site-packages/aiohttp/web.py", line 325, in _handle
    resp = yield from handler(request)
  File "/Users/jacob/.opsdroid/modules/opsdroid-modules/connector/websocket/__init__.py", line 77, in websocket_handler
    await self.opsdroid.parse(message)
  File "/Users/jacob/Projects/opsdroid/opsdroid/opsdroid/core.py", line 273, in parse
    ranked_skills = await self.get_ranked_skills(message)
  File "/Users/jacob/Projects/opsdroid/opsdroid/opsdroid/core.py", line 218, in get_ranked_skills
    dialogflow = [p for p in parsers if p["name"] == "dialogflow"
TypeError: 'NoneType' object is not iterable
```

## Versions
- **Opsdroid version:** 0.10.0
- **Python version:** Python 3.5.4
- **OS/Docker version:** macOS 10.13

--- 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/core.py`
Content:
```
1 """Core components of OpsDroid."""
2 
3 import copy
4 import logging
5 import signal
6 import sys
7 import weakref
8 import asyncio
9 
10 from opsdroid.memory import Memory
11 from opsdroid.connector import Connector
12 from opsdroid.database import Database
13 from opsdroid.loader import Loader
14 from opsdroid.parsers.always import parse_always
15 from opsdroid.parsers.regex import parse_regex
16 from opsdroid.parsers.dialogflow import parse_dialogflow
17 from opsdroid.parsers.luisai import parse_luisai
18 from opsdroid.parsers.recastai import parse_recastai
19 from opsdroid.parsers.witai import parse_witai
20 from opsdroid.parsers.crontab import parse_crontab
21 from opsdroid.const import DEFAULT_CONFIG_PATH
22 
23 
24 _LOGGER = logging.getLogger(__name__)
25 
26 
27 class OpsDroid():
28     """Root object for opsdroid."""
29 
30     # pylint: disable=too-many-instance-attributes
31     # All are reasonable in this case.
32 
33     instances = []
34 
35     def __init__(self):
36         """Start opsdroid."""
37         self.bot_name = 'opsdroid'
38         self.sys_status = 0
39         self.connectors = []
40         self.connector_tasks = []
41         self.eventloop = asyncio.get_event_loop()
42         for sig in (signal.SIGINT, signal.SIGTERM):
43             self.eventloop.add_signal_handler(sig, self.call_stop)
44         self.skills = []
45         self.memory = Memory()
46         self.loader = Loader(self)
47         self.config = {}
48         self.stats = {
49             "messages_parsed": 0,
50             "webhooks_called": 0,
51             "total_response_time": 0,
52             "total_responses": 0,
53         }
54         self.web_server = None
55         self.should_restart = False
56         self.stored_path = []
57 
58     def __enter__(self):
59         """Add self to existing instances."""
60         self.stored_path = copy.copy(sys.path)
61         if not self.__class__.instances:
62             self.__class__.instances.append(weakref.proxy(self))
63         else:
64             self.critical("opsdroid has already been started", 1)
65         return self
66 
67     def __exit__(self, exc_type, exc_value, traceback):
68         """Remove self from existing instances."""
69         sys.path = self.stored_path
70         self.__class__.instances = []
71         asyncio.set_event_loop(asyncio.new_event_loop())
72 
73     @property
74     def default_connector(self):
75         """Return the default connector."""
76         default_connector = None
77         for connector in self.connectors:
78             if "default" in connector.config and connector.config["default"]:
79                 default_connector = connector
80                 break
81         if default_connector is None:
82             default_connector = self.connectors[0]
83         return default_connector
84 
85     def exit(self):
86         """Exit application."""
87         _LOGGER.info("Exiting application with return code %s",
88                      str(self.sys_status))
89         sys.exit(self.sys_status)
90 
91     def critical(self, error, code):
92         """Exit due to unrecoverable error."""
93         self.sys_status = code
94         _LOGGER.critical(error)
95         self.exit()
96 
97     def restart(self):
98         """Restart opsdroid."""
99         self.should_restart = True
100         self.stop()
101 
102     def call_stop(self):
103         """Signal handler to call disconnect and stop."""
104         future = asyncio.ensure_future(self.disconnect())
105         future.add_done_callback(self.stop)
106         return future
107 
108     async def disconnect(self):
109         """Disconnect all the connectors."""
110         for connector in self.connectors:
111             await connector.disconnect(self)
112 
113     def stop(self, future=None):
114         """Stop the event loop."""
115         pending = asyncio.Task.all_tasks()
116         for task in pending:
117             task.cancel()
118         self.eventloop.stop()
119         print('')  # Prints a character return for return to shell
120         _LOGGER.info("Keyboard interrupt, exiting.")
121 
122     def load(self):
123         """Load configuration."""
124         self.config = self.loader.load_config_file([
125             "configuration.yaml",
126             DEFAULT_CONFIG_PATH,
127             "/etc/opsdroid/configuration.yaml"
128             ])
129 
130     def start_loop(self):
131         """Start the event loop."""
132         connectors, databases, skills = \
133             self.loader.load_modules_from_config(self.config)
134         _LOGGER.debug("Loaded %i skills", len(skills))
135         if databases is not None:
136             self.start_databases(databases)
137         self.setup_skills(skills)
138         self.start_connector_tasks(connectors)
139         self.eventloop.create_task(parse_crontab(self))
140         self.web_server.start()
141         try:
142             pending = asyncio.Task.all_tasks()
143             self.eventloop.run_until_complete(asyncio.gather(*pending))
144         except RuntimeError as error:
145             if str(error) != 'Event loop is closed':
146                 raise error
147         finally:
148             self.eventloop.close()
149 
150     def setup_skills(self, skills):
151         """Call the setup function on the passed in skills."""
152         for skill in skills:
153             try:
154                 skill["module"].setup(self)
155             except AttributeError:
156                 pass
157 
158     def start_connector_tasks(self, connectors):
159         """Start the connectors."""
160         for connector_module in connectors:
161             for _, cls in connector_module["module"].__dict__.items():
162                 if isinstance(cls, type) and \
163                    issubclass(cls, Connector) and\
164                    cls is not Connector:
165                     connector = cls(connector_module["config"])
166                     self.connectors.append(connector)
167 
168         if connectors:
169             for connector in self.connectors:
170                 self.eventloop.run_until_complete(connector.connect(self))
171             for connector in self.connectors:
172                 task = self.eventloop.create_task(connector.listen(self))
173                 self.connector_tasks.append(task)
174         else:
175             self.critical("All connectors failed to load", 1)
176 
177     def start_databases(self, databases):
178         """Start the databases."""
179         if not databases:
180             _LOGGER.debug(databases)
181             _LOGGER.warning("All databases failed to load")
182         for database_module in databases:
183             for name, cls in database_module["module"].__dict__.items():
184                 if isinstance(cls, type) and \
185                    issubclass(cls, Database) and \
186                    cls is not Database:
187                     _LOGGER.debug("Adding database: %s", name)
188                     database = cls(database_module["config"])
189                     self.memory.databases.append(database)
190                     self.eventloop.run_until_complete(database.connect(self))
191 
192     async def run_skill(self, skill, config, message):
193         """Execute a skill."""
194         # pylint: disable=broad-except
195         # We want to catch all exceptions coming from a skill module and not
196         # halt the application. If a skill throws an exception it just doesn't
197         # give a response to the user, so an error response should be given.
198         try:
199             await skill(self, config, message)
200         except Exception:
201             if message:
202                 await message.respond(
203                     "Whoops there has been an error")
204                 await message.respond(
205                     "Check the log for details")
206             _LOGGER.exception("Exception when running skill '%s' ",
207                               config["name"])
208 
209     async def get_ranked_skills(self, message):
210         """Take a message and return a ranked list of matching skills."""
211         skills = []
212         skills = skills + await parse_regex(self, message)
213 
214         if "parsers" in self.config:
215             _LOGGER.debug("Processing parsers...")
216             parsers = self.config["parsers"]
217 
218             dialogflow = [p for p in parsers if p["name"] == "dialogflow"
219                           or p["name"] == "apiai"]
220 
221             # Show deprecation message but  parse message
222             # Once it stops working remove this bit
223             apiai = [p for p in parsers if p["name"] == "apiai"]
224             if apiai:
225                 _LOGGER.warning("Api.ai is now called Dialogflow. This "
226                                 "parser will stop working in the future "
227                                 "please swap: 'name: apiai' for "
228                                 "'name: dialogflow' in configuration.yaml")
229 
230             if len(dialogflow) == 1 and \
231                     ("enabled" not in dialogflow[0] or
232                      dialogflow[0]["enabled"] is not False):
233                 _LOGGER.debug("Checking dialogflow...")
234                 skills = skills + \
235                     await parse_dialogflow(self, message, dialogflow[0])
236 
237             luisai = [p for p in parsers if p["name"] == "luisai"]
238             if len(luisai) == 1 and \
239                     ("enabled" not in luisai[0] or
240                      luisai[0]["enabled"] is not False):
241                 _LOGGER.debug("Checking luisai...")
242                 skills = skills + \
243                     await parse_luisai(self, message, luisai[0])
244 
245             recastai = [p for p in parsers if p["name"] == "recastai"]
246             if len(recastai) == 1 and \
247                     ("enabled" not in recastai[0] or
248                      recastai[0]["enabled"] is not False):
249                 _LOGGER.debug("Checking Recast.AI...")
250                 skills = skills + \
251                     await parse_recastai(self, message, recastai[0])
252 
253             witai = [p for p in parsers if p["name"] == "witai"]
254             if len(witai) == 1 and \
255                     ("enabled" not in witai[0] or
256                      witai[0]["enabled"] is not False):
257                 _LOGGER.debug("Checking wit.ai...")
258                 skills = skills + \
259                     await parse_witai(self, message, witai[0])
260 
261         return sorted(skills, key=lambda k: k["score"], reverse=True)
262 
263     async def parse(self, message):
264         """Parse a string against all skills."""
265         self.stats["messages_parsed"] = self.stats["messages_parsed"] + 1
266         tasks = []
267         if message.text.strip() != "":
268             _LOGGER.debug("Parsing input: %s", message.text)
269 
270             tasks.append(
271                 self.eventloop.create_task(parse_always(self, message)))
272 
273             ranked_skills = await self.get_ranked_skills(message)
274             if ranked_skills:
275                 tasks.append(
276                     self.eventloop.create_task(
277                         self.run_skill(ranked_skills[0]["skill"],
278                                        ranked_skills[0]["config"],
279                                        message)))
280 
281         return tasks
282
```
--- 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/opsdroid/core.py b/opsdroid/core.py
--- a/opsdroid/core.py
+++ b/opsdroid/core.py
@@ -213,7 +213,7 @@
 
         if "parsers" in self.config:
             _LOGGER.debug("Processing parsers...")
-            parsers = self.config["parsers"]
+            parsers = self.config["parsers"] or []
 
             dialogflow = [p for p in parsers if p["name"] == "dialogflow"
                           or p["name"] == "apiai"]

{"golden_diff": "diff --git a/opsdroid/core.py b/opsdroid/core.py\n--- a/opsdroid/core.py\n+++ b/opsdroid/core.py\n@@ -213,7 +213,7 @@\n \n         if \"parsers\" in self.config:\n             _LOGGER.debug(\"Processing parsers...\")\n-            parsers = self.config[\"parsers\"]\n+            parsers = self.config[\"parsers\"] or []\n \n             dialogflow = [p for p in parsers if p[\"name\"] == \"dialogflow\"\n                           or p[\"name\"] == \"apiai\"]\n", "issue": "Error if parsers is empty\n# Description\r\nIf you leave the `parsers` section of the config as an empty dictionary an error is thrown and startup fails.\r\n\r\n## Steps to Reproduce\r\nUncomment the parsers section of the config but with no actual parsers\r\n\r\n```\r\nparsers:\r\n\r\n# nothing else\r\n```\r\n\r\n## Expected Functionality\r\nThe application  should start with the default parsers only. The same as if `parsers:` is not in the config.\r\n\r\n## Experienced Functionality\r\n\r\n```\r\nDEBUG opsdroid.core: Parsing input: hi\r\nDEBUG opsdroid.core: Processing parsers...\r\nERROR aiohttp.server: Error handling request\r\nTraceback (most recent call last):\r\n  File \"/Users/jacob/.pyenv/versions/3.5.4/lib/python3.5/site-packages/aiohttp/web_protocol.py\", line 416, in start\r\n    resp = yield from self._request_handler(request)\r\n  File \"/Users/jacob/.pyenv/versions/3.5.4/lib/python3.5/site-packages/aiohttp/web.py\", line 325, in _handle\r\n    resp = yield from handler(request)\r\n  File \"/Users/jacob/.opsdroid/modules/opsdroid-modules/connector/websocket/__init__.py\", line 77, in websocket_handler\r\n    await self.opsdroid.parse(message)\r\n  File \"/Users/jacob/Projects/opsdroid/opsdroid/opsdroid/core.py\", line 273, in parse\r\n    ranked_skills = await self.get_ranked_skills(message)\r\n  File \"/Users/jacob/Projects/opsdroid/opsdroid/opsdroid/core.py\", line 218, in get_ranked_skills\r\n    dialogflow = [p for p in parsers if p[\"name\"] == \"dialogflow\"\r\nTypeError: 'NoneType' object is not iterable\r\n```\r\n\r\n## Versions\r\n- **Opsdroid version:** 0.10.0\r\n- **Python version:** Python 3.5.4\r\n- **OS/Docker version:** macOS 10.13\n", "before_files": [{"content": "\"\"\"Core components of OpsDroid.\"\"\"\n\nimport copy\nimport logging\nimport signal\nimport sys\nimport weakref\nimport asyncio\n\nfrom opsdroid.memory import Memory\nfrom opsdroid.connector import Connector\nfrom opsdroid.database import Database\nfrom opsdroid.loader import Loader\nfrom opsdroid.parsers.always import parse_always\nfrom opsdroid.parsers.regex import parse_regex\nfrom opsdroid.parsers.dialogflow import parse_dialogflow\nfrom opsdroid.parsers.luisai import parse_luisai\nfrom opsdroid.parsers.recastai import parse_recastai\nfrom opsdroid.parsers.witai import parse_witai\nfrom opsdroid.parsers.crontab import parse_crontab\nfrom opsdroid.const import DEFAULT_CONFIG_PATH\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\nclass OpsDroid():\n    \"\"\"Root object for opsdroid.\"\"\"\n\n    # pylint: disable=too-many-instance-attributes\n    # All are reasonable in this case.\n\n    instances = []\n\n    def __init__(self):\n        \"\"\"Start opsdroid.\"\"\"\n        self.bot_name = 'opsdroid'\n        self.sys_status = 0\n        self.connectors = []\n        self.connector_tasks = []\n        self.eventloop = asyncio.get_event_loop()\n        for sig in (signal.SIGINT, signal.SIGTERM):\n            self.eventloop.add_signal_handler(sig, self.call_stop)\n        self.skills = []\n        self.memory = Memory()\n        self.loader = Loader(self)\n        self.config = {}\n        self.stats = {\n            \"messages_parsed\": 0,\n            \"webhooks_called\": 0,\n            \"total_response_time\": 0,\n            \"total_responses\": 0,\n        }\n        self.web_server = None\n        self.should_restart = False\n        self.stored_path = []\n\n    def __enter__(self):\n        \"\"\"Add self to existing instances.\"\"\"\n        self.stored_path = copy.copy(sys.path)\n        if not self.__class__.instances:\n            self.__class__.instances.append(weakref.proxy(self))\n        else:\n            self.critical(\"opsdroid has already been started\", 1)\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        \"\"\"Remove self from existing instances.\"\"\"\n        sys.path = self.stored_path\n        self.__class__.instances = []\n        asyncio.set_event_loop(asyncio.new_event_loop())\n\n    @property\n    def default_connector(self):\n        \"\"\"Return the default connector.\"\"\"\n        default_connector = None\n        for connector in self.connectors:\n            if \"default\" in connector.config and connector.config[\"default\"]:\n                default_connector = connector\n                break\n        if default_connector is None:\n            default_connector = self.connectors[0]\n        return default_connector\n\n    def exit(self):\n        \"\"\"Exit application.\"\"\"\n        _LOGGER.info(\"Exiting application with return code %s\",\n                     str(self.sys_status))\n        sys.exit(self.sys_status)\n\n    def critical(self, error, code):\n        \"\"\"Exit due to unrecoverable error.\"\"\"\n        self.sys_status = code\n        _LOGGER.critical(error)\n        self.exit()\n\n    def restart(self):\n        \"\"\"Restart opsdroid.\"\"\"\n        self.should_restart = True\n        self.stop()\n\n    def call_stop(self):\n        \"\"\"Signal handler to call disconnect and stop.\"\"\"\n        future = asyncio.ensure_future(self.disconnect())\n        future.add_done_callback(self.stop)\n        return future\n\n    async def disconnect(self):\n        \"\"\"Disconnect all the connectors.\"\"\"\n        for connector in self.connectors:\n            await connector.disconnect(self)\n\n    def stop(self, future=None):\n        \"\"\"Stop the event loop.\"\"\"\n        pending = asyncio.Task.all_tasks()\n        for task in pending:\n            task.cancel()\n        self.eventloop.stop()\n        print('')  # Prints a character return for return to shell\n        _LOGGER.info(\"Keyboard interrupt, exiting.\")\n\n    def load(self):\n        \"\"\"Load configuration.\"\"\"\n        self.config = self.loader.load_config_file([\n            \"configuration.yaml\",\n            DEFAULT_CONFIG_PATH,\n            \"/etc/opsdroid/configuration.yaml\"\n            ])\n\n    def start_loop(self):\n        \"\"\"Start the event loop.\"\"\"\n        connectors, databases, skills = \\\n            self.loader.load_modules_from_config(self.config)\n        _LOGGER.debug(\"Loaded %i skills\", len(skills))\n        if databases is not None:\n            self.start_databases(databases)\n        self.setup_skills(skills)\n        self.start_connector_tasks(connectors)\n        self.eventloop.create_task(parse_crontab(self))\n        self.web_server.start()\n        try:\n            pending = asyncio.Task.all_tasks()\n            self.eventloop.run_until_complete(asyncio.gather(*pending))\n        except RuntimeError as error:\n            if str(error) != 'Event loop is closed':\n                raise error\n        finally:\n            self.eventloop.close()\n\n    def setup_skills(self, skills):\n        \"\"\"Call the setup function on the passed in skills.\"\"\"\n        for skill in skills:\n            try:\n                skill[\"module\"].setup(self)\n            except AttributeError:\n                pass\n\n    def start_connector_tasks(self, connectors):\n        \"\"\"Start the connectors.\"\"\"\n        for connector_module in connectors:\n            for _, cls in connector_module[\"module\"].__dict__.items():\n                if isinstance(cls, type) and \\\n                   issubclass(cls, Connector) and\\\n                   cls is not Connector:\n                    connector = cls(connector_module[\"config\"])\n                    self.connectors.append(connector)\n\n        if connectors:\n            for connector in self.connectors:\n                self.eventloop.run_until_complete(connector.connect(self))\n            for connector in self.connectors:\n                task = self.eventloop.create_task(connector.listen(self))\n                self.connector_tasks.append(task)\n        else:\n            self.critical(\"All connectors failed to load\", 1)\n\n    def start_databases(self, databases):\n        \"\"\"Start the databases.\"\"\"\n        if not databases:\n            _LOGGER.debug(databases)\n            _LOGGER.warning(\"All databases failed to load\")\n        for database_module in databases:\n            for name, cls in database_module[\"module\"].__dict__.items():\n                if isinstance(cls, type) and \\\n                   issubclass(cls, Database) and \\\n                   cls is not Database:\n                    _LOGGER.debug(\"Adding database: %s\", name)\n                    database = cls(database_module[\"config\"])\n                    self.memory.databases.append(database)\n                    self.eventloop.run_until_complete(database.connect(self))\n\n    async def run_skill(self, skill, config, message):\n        \"\"\"Execute a skill.\"\"\"\n        # pylint: disable=broad-except\n        # We want to catch all exceptions coming from a skill module and not\n        # halt the application. If a skill throws an exception it just doesn't\n        # give a response to the user, so an error response should be given.\n        try:\n            await skill(self, config, message)\n        except Exception:\n            if message:\n                await message.respond(\n                    \"Whoops there has been an error\")\n                await message.respond(\n                    \"Check the log for details\")\n            _LOGGER.exception(\"Exception when running skill '%s' \",\n                              config[\"name\"])\n\n    async def get_ranked_skills(self, message):\n        \"\"\"Take a message and return a ranked list of matching skills.\"\"\"\n        skills = []\n        skills = skills + await parse_regex(self, message)\n\n        if \"parsers\" in self.config:\n            _LOGGER.debug(\"Processing parsers...\")\n            parsers = self.config[\"parsers\"]\n\n            dialogflow = [p for p in parsers if p[\"name\"] == \"dialogflow\"\n                          or p[\"name\"] == \"apiai\"]\n\n            # Show deprecation message but  parse message\n            # Once it stops working remove this bit\n            apiai = [p for p in parsers if p[\"name\"] == \"apiai\"]\n            if apiai:\n                _LOGGER.warning(\"Api.ai is now called Dialogflow. This \"\n                                \"parser will stop working in the future \"\n                                \"please swap: 'name: apiai' for \"\n                                \"'name: dialogflow' in configuration.yaml\")\n\n            if len(dialogflow) == 1 and \\\n                    (\"enabled\" not in dialogflow[0] or\n                     dialogflow[0][\"enabled\"] is not False):\n                _LOGGER.debug(\"Checking dialogflow...\")\n                skills = skills + \\\n                    await parse_dialogflow(self, message, dialogflow[0])\n\n            luisai = [p for p in parsers if p[\"name\"] == \"luisai\"]\n            if len(luisai) == 1 and \\\n                    (\"enabled\" not in luisai[0] or\n                     luisai[0][\"enabled\"] is not False):\n                _LOGGER.debug(\"Checking luisai...\")\n                skills = skills + \\\n                    await parse_luisai(self, message, luisai[0])\n\n            recastai = [p for p in parsers if p[\"name\"] == \"recastai\"]\n            if len(recastai) == 1 and \\\n                    (\"enabled\" not in recastai[0] or\n                     recastai[0][\"enabled\"] is not False):\n                _LOGGER.debug(\"Checking Recast.AI...\")\n                skills = skills + \\\n                    await parse_recastai(self, message, recastai[0])\n\n            witai = [p for p in parsers if p[\"name\"] == \"witai\"]\n            if len(witai) == 1 and \\\n                    (\"enabled\" not in witai[0] or\n                     witai[0][\"enabled\"] is not False):\n                _LOGGER.debug(\"Checking wit.ai...\")\n                skills = skills + \\\n                    await parse_witai(self, message, witai[0])\n\n        return sorted(skills, key=lambda k: k[\"score\"], reverse=True)\n\n    async def parse(self, message):\n        \"\"\"Parse a string against all skills.\"\"\"\n        self.stats[\"messages_parsed\"] = self.stats[\"messages_parsed\"] + 1\n        tasks = []\n        if message.text.strip() != \"\":\n            _LOGGER.debug(\"Parsing input: %s\", message.text)\n\n            tasks.append(\n                self.eventloop.create_task(parse_always(self, message)))\n\n            ranked_skills = await self.get_ranked_skills(message)\n            if ranked_skills:\n                tasks.append(\n                    self.eventloop.create_task(\n                        self.run_skill(ranked_skills[0][\"skill\"],\n                                       ranked_skills[0][\"config\"],\n                                       message)))\n\n        return tasks\n", "path": "opsdroid/core.py"}], "after_files": [{"content": "\"\"\"Core components of OpsDroid.\"\"\"\n\nimport copy\nimport logging\nimport signal\nimport sys\nimport weakref\nimport asyncio\n\nfrom opsdroid.memory import Memory\nfrom opsdroid.connector import Connector\nfrom opsdroid.database import Database\nfrom opsdroid.loader import Loader\nfrom opsdroid.parsers.always import parse_always\nfrom opsdroid.parsers.regex import parse_regex\nfrom opsdroid.parsers.dialogflow import parse_dialogflow\nfrom opsdroid.parsers.luisai import parse_luisai\nfrom opsdroid.parsers.recastai import parse_recastai\nfrom opsdroid.parsers.witai import parse_witai\nfrom opsdroid.parsers.crontab import parse_crontab\nfrom opsdroid.const import DEFAULT_CONFIG_PATH\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\nclass OpsDroid():\n    \"\"\"Root object for opsdroid.\"\"\"\n\n    # pylint: disable=too-many-instance-attributes\n    # All are reasonable in this case.\n\n    instances = []\n\n    def __init__(self):\n        \"\"\"Start opsdroid.\"\"\"\n        self.bot_name = 'opsdroid'\n        self.sys_status = 0\n        self.connectors = []\n        self.connector_tasks = []\n        self.eventloop = asyncio.get_event_loop()\n        for sig in (signal.SIGINT, signal.SIGTERM):\n            self.eventloop.add_signal_handler(sig, self.call_stop)\n        self.skills = []\n        self.memory = Memory()\n        self.loader = Loader(self)\n        self.config = {}\n        self.stats = {\n            \"messages_parsed\": 0,\n            \"webhooks_called\": 0,\n            \"total_response_time\": 0,\n            \"total_responses\": 0,\n        }\n        self.web_server = None\n        self.should_restart = False\n        self.stored_path = []\n\n    def __enter__(self):\n        \"\"\"Add self to existing instances.\"\"\"\n        self.stored_path = copy.copy(sys.path)\n        if not self.__class__.instances:\n            self.__class__.instances.append(weakref.proxy(self))\n        else:\n            self.critical(\"opsdroid has already been started\", 1)\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        \"\"\"Remove self from existing instances.\"\"\"\n        sys.path = self.stored_path\n        self.__class__.instances = []\n        asyncio.set_event_loop(asyncio.new_event_loop())\n\n    @property\n    def default_connector(self):\n        \"\"\"Return the default connector.\"\"\"\n        default_connector = None\n        for connector in self.connectors:\n            if \"default\" in connector.config and connector.config[\"default\"]:\n                default_connector = connector\n                break\n        if default_connector is None:\n            default_connector = self.connectors[0]\n        return default_connector\n\n    def exit(self):\n        \"\"\"Exit application.\"\"\"\n        _LOGGER.info(\"Exiting application with return code %s\",\n                     str(self.sys_status))\n        sys.exit(self.sys_status)\n\n    def critical(self, error, code):\n        \"\"\"Exit due to unrecoverable error.\"\"\"\n        self.sys_status = code\n        _LOGGER.critical(error)\n        self.exit()\n\n    def restart(self):\n        \"\"\"Restart opsdroid.\"\"\"\n        self.should_restart = True\n        self.stop()\n\n    def call_stop(self):\n        \"\"\"Signal handler to call disconnect and stop.\"\"\"\n        future = asyncio.ensure_future(self.disconnect())\n        future.add_done_callback(self.stop)\n        return future\n\n    async def disconnect(self):\n        \"\"\"Disconnect all the connectors.\"\"\"\n        for connector in self.connectors:\n            await connector.disconnect(self)\n\n    def stop(self, future=None):\n        \"\"\"Stop the event loop.\"\"\"\n        pending = asyncio.Task.all_tasks()\n        for task in pending:\n            task.cancel()\n        self.eventloop.stop()\n        print('')  # Prints a character return for return to shell\n        _LOGGER.info(\"Keyboard interrupt, exiting.\")\n\n    def load(self):\n        \"\"\"Load configuration.\"\"\"\n        self.config = self.loader.load_config_file([\n            \"configuration.yaml\",\n            DEFAULT_CONFIG_PATH,\n            \"/etc/opsdroid/configuration.yaml\"\n            ])\n\n    def start_loop(self):\n        \"\"\"Start the event loop.\"\"\"\n        connectors, databases, skills = \\\n            self.loader.load_modules_from_config(self.config)\n        _LOGGER.debug(\"Loaded %i skills\", len(skills))\n        if databases is not None:\n            self.start_databases(databases)\n        self.setup_skills(skills)\n        self.start_connector_tasks(connectors)\n        self.eventloop.create_task(parse_crontab(self))\n        self.web_server.start()\n        try:\n            pending = asyncio.Task.all_tasks()\n            self.eventloop.run_until_complete(asyncio.gather(*pending))\n        except RuntimeError as error:\n            if str(error) != 'Event loop is closed':\n                raise error\n        finally:\n            self.eventloop.close()\n\n    def setup_skills(self, skills):\n        \"\"\"Call the setup function on the passed in skills.\"\"\"\n        for skill in skills:\n            try:\n                skill[\"module\"].setup(self)\n            except AttributeError:\n                pass\n\n    def start_connector_tasks(self, connectors):\n        \"\"\"Start the connectors.\"\"\"\n        for connector_module in connectors:\n            for _, cls in connector_module[\"module\"].__dict__.items():\n                if isinstance(cls, type) and \\\n                   issubclass(cls, Connector) and\\\n                   cls is not Connector:\n                    connector = cls(connector_module[\"config\"])\n                    self.connectors.append(connector)\n\n        if connectors:\n            for connector in self.connectors:\n                self.eventloop.run_until_complete(connector.connect(self))\n            for connector in self.connectors:\n                task = self.eventloop.create_task(connector.listen(self))\n                self.connector_tasks.append(task)\n        else:\n            self.critical(\"All connectors failed to load\", 1)\n\n    def start_databases(self, databases):\n        \"\"\"Start the databases.\"\"\"\n        if not databases:\n            _LOGGER.debug(databases)\n            _LOGGER.warning(\"All databases failed to load\")\n        for database_module in databases:\n            for name, cls in database_module[\"module\"].__dict__.items():\n                if isinstance(cls, type) and \\\n                   issubclass(cls, Database) and \\\n                   cls is not Database:\n                    _LOGGER.debug(\"Adding database: %s\", name)\n                    database = cls(database_module[\"config\"])\n                    self.memory.databases.append(database)\n                    self.eventloop.run_until_complete(database.connect(self))\n\n    async def run_skill(self, skill, config, message):\n        \"\"\"Execute a skill.\"\"\"\n        # pylint: disable=broad-except\n        # We want to catch all exceptions coming from a skill module and not\n        # halt the application. If a skill throws an exception it just doesn't\n        # give a response to the user, so an error response should be given.\n        try:\n            await skill(self, config, message)\n        except Exception:\n            if message:\n                await message.respond(\n                    \"Whoops there has been an error\")\n                await message.respond(\n                    \"Check the log for details\")\n            _LOGGER.exception(\"Exception when running skill '%s' \",\n                              config[\"name\"])\n\n    async def get_ranked_skills(self, message):\n        \"\"\"Take a message and return a ranked list of matching skills.\"\"\"\n        skills = []\n        skills = skills + await parse_regex(self, message)\n\n        if \"parsers\" in self.config:\n            _LOGGER.debug(\"Processing parsers...\")\n            parsers = self.config[\"parsers\"] or []\n\n            dialogflow = [p for p in parsers if p[\"name\"] == \"dialogflow\"\n                          or p[\"name\"] == \"apiai\"]\n\n            # Show deprecation message but  parse message\n            # Once it stops working remove this bit\n            apiai = [p for p in parsers if p[\"name\"] == \"apiai\"]\n            if apiai:\n                _LOGGER.warning(\"Api.ai is now called Dialogflow. This \"\n                                \"parser will stop working in the future \"\n                                \"please swap: 'name: apiai' for \"\n                                \"'name: dialogflow' in configuration.yaml\")\n\n            if len(dialogflow) == 1 and \\\n                    (\"enabled\" not in dialogflow[0] or\n                     dialogflow[0][\"enabled\"] is not False):\n                _LOGGER.debug(\"Checking dialogflow...\")\n                skills = skills + \\\n                    await parse_dialogflow(self, message, dialogflow[0])\n\n            luisai = [p for p in parsers if p[\"name\"] == \"luisai\"]\n            if len(luisai) == 1 and \\\n                    (\"enabled\" not in luisai[0] or\n                     luisai[0][\"enabled\"] is not False):\n                _LOGGER.debug(\"Checking luisai...\")\n                skills = skills + \\\n                    await parse_luisai(self, message, luisai[0])\n\n            recastai = [p for p in parsers if p[\"name\"] == \"recastai\"]\n            if len(recastai) == 1 and \\\n                    (\"enabled\" not in recastai[0] or\n                     recastai[0][\"enabled\"] is not False):\n                _LOGGER.debug(\"Checking Recast.AI...\")\n                skills = skills + \\\n                    await parse_recastai(self, message, recastai[0])\n\n            witai = [p for p in parsers if p[\"name\"] == \"witai\"]\n            if len(witai) == 1 and \\\n                    (\"enabled\" not in witai[0] or\n                     witai[0][\"enabled\"] is not False):\n                _LOGGER.debug(\"Checking wit.ai...\")\n                skills = skills + \\\n                    await parse_witai(self, message, witai[0])\n\n        return sorted(skills, key=lambda k: k[\"score\"], reverse=True)\n\n    async def parse(self, message):\n        \"\"\"Parse a string against all skills.\"\"\"\n        self.stats[\"messages_parsed\"] = self.stats[\"messages_parsed\"] + 1\n        tasks = []\n        if message.text.strip() != \"\":\n            _LOGGER.debug(\"Parsing input: %s\", message.text)\n\n            tasks.append(\n                self.eventloop.create_task(parse_always(self, message)))\n\n            ranked_skills = await self.get_ranked_skills(message)\n            if ranked_skills:\n                tasks.append(\n                    self.eventloop.create_task(\n                        self.run_skill(ranked_skills[0][\"skill\"],\n                                       ranked_skills[0][\"config\"],\n                                       message)))\n\n        return tasks\n", "path": "opsdroid/core.py"}]}