From 44f63992dac44a4479f3367e6006bedc893406bb Mon Sep 17 00:00:00 2001
From: Eeshan Garg <jerryguitarist@gmail.com>
Date: Sat, 22 Jul 2017 17:51:07 -0230
Subject: [PATCH] zulip_bots/run: Support importing bot modules by path.

The zulip-run-bot script now supports passing in a --path-to-bot
option. This allows users to specify the path to the source file
for their own custom bots, the first step towards being able to
support out-of-tree bots.

To run an existing bot in the zulip_bots package, just passing in
the name of the bot should suffice.
---
 zulip_bots/zulip_bots/run.py | 44 ++++++++++++++++++++++++++++++++----
 1 file changed, 40 insertions(+), 4 deletions(-)

diff --git a/zulip_bots/zulip_bots/run.py b/zulip_bots/zulip_bots/run.py
index 1535341d..9deee125 100755
--- a/zulip_bots/zulip_bots/run.py
+++ b/zulip_bots/zulip_bots/run.py
@@ -7,10 +7,29 @@ import optparse
 import sys
 from types import ModuleType
 from importlib import import_module
+from os.path import basename, splitext
+
+import six
 
 from zulip_bots.lib import run_message_handler_for_bot
 
 
+def import_module_from_source(path, name=None):
+    if name is None:
+        name = splitext(basename(path))[0]
+
+    if six.PY2:
+        import imp
+        module = imp.load_source(name, path)
+        return module
+    else:
+        import importlib.util
+        spec = importlib.util.spec_from_file_location(name, path)
+        module = importlib.util.module_from_spec(spec)
+        spec.loader.exec_module(module)
+        return module
+
+
 def parse_args():
     usage = '''
         zulip-run-bot <bot_name>
@@ -33,12 +52,22 @@ def parse_args():
                       action='store',
                       help='(alternate config file to ~/.zuliprc)')
 
+    parser.add_option('--path-to-bot',
+                      action='store',
+                      help='path to the file with the bot handler class')
+
     parser.add_option('--force',
                       action='store_true',
                       help='Try running the bot even if dependencies install fails.')
     (options, args) = parser.parse_args()
-    if not args:
-        parser.error('You must specify the name of the bot!')
+
+    if not args and not options.path_to_bot:
+        error_message = """
+You must either specify the name of an existing bot or
+specify a path to the file (--path-to-bot) that contains
+the bot handler class.
+"""
+        parser.error(error_message)
 
     return (options, args)
 
@@ -46,9 +75,16 @@ def parse_args():
 def main():
     # type: () -> None
     (options, args) = parse_args()
-    bot_name = args[0]
 
-    lib_module = import_module('zulip_bots.bots.{bot}.{bot}'.format(bot=bot_name))
+    bot_name = None
+    if args:
+        bot_name = args[0]
+
+    if options.path_to_bot:
+        lib_module = import_module_from_source(options.path_to_bot, name=bot_name)
+    else:
+        lib_module = import_module('zulip_bots.bots.{bot}.{bot}'.format(bot=bot_name))
+
     if not options.quiet:
         logging.basicConfig(stream=sys.stdout, level=logging.INFO)