diff --git a/modules/cmd_server.py b/modules/cmd_server.py
deleted file mode 100644
index 8fdadb5..0000000
--- a/modules/cmd_server.py
+++ /dev/null
@@ -1,204 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Nemubot is a smart and modulable IM bot.
-# Copyright (C) 2012-2015 Mercier Pierre-Olivier
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-import traceback
-import sys
-
-from nemubot.hooks import hook
-
-nemubotversion = 3.4
-NODATA = True
-
-
-def getserver(toks, context, prompt, mandatory=False, **kwargs):
- """Choose the server in toks or prompt.
- This function modify the tokens list passed as argument"""
-
- if len(toks) > 1 and toks[1] in context.servers:
- return context.servers[toks.pop(1)]
- elif not mandatory or prompt.selectedServer:
- return prompt.selectedServer
- else:
- from nemubot.prompt.error import PromptError
- raise PromptError("Please SELECT a server or give its name in argument.")
-
-
-@hook("prompt_cmd", "close")
-def close(toks, context, **kwargs):
- """Disconnect and forget (remove from the servers list) the server"""
- srv = getserver(toks, context=context, mandatory=True, **kwargs)
-
- if srv.close():
- del context.servers[srv.id]
- return 0
- return 1
-
-
-@hook("prompt_cmd", "connect")
-def connect(toks, **kwargs):
- """Make the connexion to a server"""
- srv = getserver(toks, mandatory=True, **kwargs)
-
- return not srv.open()
-
-
-@hook("prompt_cmd", "disconnect")
-def disconnect(toks, **kwargs):
- """Close the connection to a server"""
- srv = getserver(toks, mandatory=True, **kwargs)
-
- return not srv.close()
-
-
-@hook("prompt_cmd", "discover")
-def discover(toks, context, **kwargs):
- """Discover a new bot on a server"""
- srv = getserver(toks, context=context, mandatory=True, **kwargs)
-
- if len(toks) > 1 and "!" in toks[1]:
- bot = context.add_networkbot(srv, name)
- return not bot.connect()
- else:
- print(" %s is not a valid fullname, for example: "
- "nemubot!nemubotV3@bot.nemunai.re" % ''.join(toks[1:1]))
- return 1
-
-
-@hook("prompt_cmd", "join")
-@hook("prompt_cmd", "leave")
-@hook("prompt_cmd", "part")
-def join(toks, **kwargs):
- """Join or leave a channel"""
- srv = getserver(toks, mandatory=True, **kwargs)
-
- if len(toks) <= 2:
- print("%s: not enough arguments." % toks[0])
- return 1
-
- if toks[0] == "join":
- if len(toks) > 2:
- srv.write("JOIN %s %s" % (toks[1], toks[2]))
- else:
- srv.write("JOIN %s" % toks[1])
-
- elif toks[0] == "leave" or toks[0] == "part":
- if len(toks) > 2:
- srv.write("PART %s :%s" % (toks[1], " ".join(toks[2:])))
- else:
- srv.write("PART %s" % toks[1])
-
- return 0
-
-
-@hook("prompt_cmd", "save")
-def save_mod(toks, context, **kwargs):
- """Force save module data"""
- if len(toks) < 2:
- print("save: not enough arguments.")
- return 1
-
- wrn = 0
- for mod in toks[1:]:
- if mod in context.modules:
- context.modules[mod].save()
- print("save: module `%s´ saved successfully" % mod)
- else:
- wrn += 1
- print("save: no module named `%s´" % mod)
- return wrn
-
-
-@hook("prompt_cmd", "send")
-def send(toks, **kwargs):
- """Send a message on a channel"""
- srv = getserver(toks, mandatory=True, **kwargs)
-
- # Check the server is connected
- if not srv.connected:
- print ("send: server `%s' not connected." % srv.id)
- return 2
-
- if len(toks) <= 3:
- print ("send: not enough arguments.")
- return 1
-
- if toks[1] not in srv.channels:
- print ("send: channel `%s' not authorized in server `%s'."
- % (toks[1], srv.id))
- return 3
-
- from nemubot.message import Text
- srv.send_response(Text(" ".join(toks[2:]), server=None,
- to=[toks[1]]))
- return 0
-
-
-@hook("prompt_cmd", "zap")
-def zap(toks, **kwargs):
- """Hard change connexion state"""
- srv = getserver(toks, mandatory=True, **kwargs)
-
- srv.connected = not srv.connected
-
-
-@hook("prompt_cmd", "top")
-def top(toks, context, **kwargs):
- """Display consumers load information"""
- print("Queue size: %d, %d thread(s) running (counter: %d)" %
- (context.cnsr_queue.qsize(),
- len(context.cnsr_thrd),
- context.cnsr_thrd_size))
- if len(context.events) > 0:
- print("Events registered: %d, next in %d seconds" %
- (len(context.events),
- context.events[0].time_left.seconds))
- else:
- print("No events registered")
-
- for th in context.cnsr_thrd:
- if th.is_alive():
- print(("#" * 15 + " Stack trace for thread %u " + "#" * 15) %
- th.ident)
- traceback.print_stack(sys._current_frames()[th.ident])
-
-
-@hook("prompt_cmd", "netstat")
-def netstat(toks, context, **kwargs):
- """Display sockets in use and many other things"""
- if len(context.network) > 0:
- print("Distant bots connected: %d:" % len(context.network))
- for name, bot in context.network.items():
- print("# %s:" % name)
- print(" * Declared hooks:")
- lvl = 0
- for hlvl in bot.hooks:
- lvl += 1
- for hook in (hlvl.all_pre + hlvl.all_post + hlvl.cmd_rgxp +
- hlvl.cmd_default + hlvl.ask_rgxp +
- hlvl.ask_default + hlvl.msg_rgxp +
- hlvl.msg_default):
- print(" %s- %s" % (' ' * lvl * 2, hook))
- for kind in ["irc_hook", "cmd_hook", "ask_hook", "msg_hook"]:
- print(" %s- <%s> %s" % (' ' * lvl * 2, kind,
- ", ".join(hlvl.__dict__[kind].keys())))
- print(" * My tag: %d" % bot.my_tag)
- print(" * Tags in use (%d):" % bot.inc_tag)
- for tag, (cmd, data) in bot.tags.items():
- print(" - %11s: %s « %s »" % (tag, cmd, data))
- else:
- print("No distant bot connected")
diff --git a/nemubot/__init__.py b/nemubot/__init__.py
index 84403e0..9f02039 100644
--- a/nemubot/__init__.py
+++ b/nemubot/__init__.py
@@ -20,6 +20,7 @@ __version__ = '4.0.dev3'
__author__ = 'nemunaire'
from nemubot.modulecontext import ModuleContext
+
context = ModuleContext(None, None)
@@ -40,12 +41,112 @@ def requires_version(min=None, max=None):
"but this is nemubot v%s." % (str(max), __version__))
+def attach(pid, socketfile):
+ import socket
+ import sys
+
+ print("nemubot is already launched with PID %d. Attaching to Unix socket at: %s" % (pid, socketfile))
+
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ try:
+ sock.connect(socketfile)
+ except socket.error as e:
+ sys.stderr.write(str(e))
+ sys.stderr.write("\n")
+ return 1
+
+ from select import select
+ try:
+ print("Connection established.")
+ while True:
+ rl, wl, xl = select([sys.stdin, sock], [], [])
+
+ if sys.stdin in rl:
+ line = sys.stdin.readline().strip()
+ if line == "exit" or line == "quit":
+ return 0
+ elif line == "reload":
+ import os, signal
+ os.kill(pid, signal.SIGHUP)
+ print("Reload signal sent. Please wait...")
+
+ elif line == "shutdown":
+ import os, signal
+ os.kill(pid, signal.SIGTERM)
+ print("Shutdown signal sent. Please wait...")
+
+ elif line == "kill":
+ import os, signal
+ os.kill(pid, signal.SIGKILL)
+ print("Signal sent...")
+ return 0
+
+ elif line == "stack" or line == "stacks":
+ import os, signal
+ os.kill(pid, signal.SIGUSR1)
+ print("Debug signal sent. Consult logs.")
+
+ else:
+ sock.send(line.encode() + b'\r\n')
+
+ if sock in rl:
+ sys.stdout.write(sock.recv(2048).decode())
+ except KeyboardInterrupt:
+ pass
+ except:
+ return 1
+ finally:
+ sock.close()
+ return 0
+
+
+def daemonize():
+ """Detach the running process to run as a daemon
+ """
+
+ import os
+ import sys
+
+ try:
+ pid = os.fork()
+ if pid > 0:
+ sys.exit(0)
+ except OSError as err:
+ sys.stderr.write("Unable to fork: %s\n" % err)
+ sys.exit(1)
+
+ os.setsid()
+ os.umask(0)
+ os.chdir('/')
+
+ try:
+ pid = os.fork()
+ if pid > 0:
+ sys.exit(0)
+ except OSError as err:
+ sys.stderr.write("Unable to fork: %s\n" % err)
+ sys.exit(1)
+
+ sys.stdout.flush()
+ sys.stderr.flush()
+ si = open(os.devnull, 'r')
+ so = open(os.devnull, 'a+')
+ se = open(os.devnull, 'a+')
+
+ os.dup2(si.fileno(), sys.stdin.fileno())
+ os.dup2(so.fileno(), sys.stdout.fileno())
+ os.dup2(se.fileno(), sys.stderr.fileno())
+
+
def reload():
"""Reload code of all Python modules used by nemubot
"""
import imp
+ import nemubot.bot
+ imp.reload(nemubot.bot)
+
import nemubot.channel
imp.reload(nemubot.channel)
@@ -71,15 +172,14 @@ def reload():
nemubot.message.reload()
- import nemubot.prompt
- imp.reload(nemubot.prompt)
-
- nemubot.prompt.reload()
-
import nemubot.server
- rl, wl, xl = nemubot.server._rlist, nemubot.server._wlist, nemubot.server._xlist
+ rl = nemubot.server._rlist
+ wl = nemubot.server._wlist
+ xl = nemubot.server._xlist
imp.reload(nemubot.server)
- nemubot.server._rlist, nemubot.server._wlist, nemubot.server._xlist = rl, wl, xl
+ nemubot.server._rlist = rl
+ nemubot.server._wlist = wl
+ nemubot.server._xlist = xl
nemubot.server.reload()
diff --git a/nemubot/__main__.py b/nemubot/__main__.py
index 992c3ad..efc31bd 100644
--- a/nemubot/__main__.py
+++ b/nemubot/__main__.py
@@ -18,6 +18,7 @@
def main():
import os
+ import signal
import sys
# Parse command line arguments
@@ -38,6 +39,15 @@ def main():
default=["./modules/"],
help="directory to use as modules store")
+ parser.add_argument("-d", "--debug", action="store_true",
+ help="don't deamonize, keep in foreground")
+
+ parser.add_argument("-P", "--pidfile", default="./nemubot.pid",
+ help="Path to the file where store PID")
+
+ parser.add_argument("-S", "--socketfile", default="./nemubot.sock",
+ help="path where open the socket for internal communication")
+
parser.add_argument("-l", "--logfile", default="./nemubot.log",
help="Path to store logs")
@@ -60,10 +70,34 @@ def main():
# Resolve relatives paths
args.data_path = os.path.abspath(os.path.expanduser(args.data_path))
+ args.pidfile = os.path.abspath(os.path.expanduser(args.pidfile))
+ args.socketfile = os.path.abspath(os.path.expanduser(args.socketfile))
args.logfile = os.path.abspath(os.path.expanduser(args.logfile))
args.files = [ x for x in map(os.path.abspath, args.files)]
args.modules_path = [ x for x in map(os.path.abspath, args.modules_path)]
+ # Check if an instance is already launched
+ if args.pidfile is not None and os.path.isfile(args.pidfile):
+ with open(args.pidfile, "r") as f:
+ pid = int(f.readline())
+ try:
+ os.kill(pid, 0)
+ except OSError:
+ pass
+ else:
+ from nemubot import attach
+ sys.exit(attach(pid, args.socketfile))
+
+ # Daemonize
+ if not args.debug:
+ from nemubot import daemonize
+ daemonize()
+
+ # Store PID to pidfile
+ if args.pidfile is not None:
+ with open(args.pidfile, "w+") as f:
+ f.write(str(os.getpid()))
+
# Setup loggin interface
import logging
logger = logging.getLogger("nemubot")
@@ -72,11 +106,12 @@ def main():
formatter = logging.Formatter(
'%(asctime)s %(name)s %(levelname)s %(message)s')
- ch = logging.StreamHandler()
- ch.setFormatter(formatter)
- if args.verbose < 2:
- ch.setLevel(logging.INFO)
- logger.addHandler(ch)
+ if args.debug:
+ ch = logging.StreamHandler()
+ ch.setFormatter(formatter)
+ if args.verbose < 2:
+ ch.setLevel(logging.INFO)
+ logger.addHandler(ch)
fh = logging.FileHandler(args.logfile)
fh.setFormatter(formatter)
@@ -100,13 +135,10 @@ def main():
if args.no_connect:
context.noautoconnect = True
- # Load the prompt
- import nemubot.prompt
- prmpt = nemubot.prompt.Prompt()
-
# Register the hook for futur import
from nemubot.importer import ModuleFinder
- sys.meta_path.append(ModuleFinder(context.modules_paths, context.add_module))
+ module_finder = ModuleFinder(context.modules_paths, context.add_module)
+ sys.meta_path.append(module_finder)
# Load requested configuration files
for path in args.files:
@@ -119,36 +151,62 @@ def main():
for module in args.module:
__import__(module)
- print ("Nemubot v%s ready, my PID is %i!" % (nemubot.__version__,
- os.getpid()))
- while True:
- from nemubot.prompt.reset import PromptReset
- try:
- context.start()
- if prmpt.run(context):
- break
- except PromptReset as e:
- if e.type == "quit":
- break
+ # Signals handling
+ def sigtermhandler(signum, frame):
+ """On SIGTERM and SIGINT, quit nicely"""
+ context.quit()
+ signal.signal(signal.SIGINT, sigtermhandler)
+ signal.signal(signal.SIGTERM, sigtermhandler)
- try:
- import imp
- # Reload all other modules
- imp.reload(nemubot)
- imp.reload(nemubot.prompt)
- nemubot.reload()
- import nemubot.bot
- context = nemubot.bot.hotswap(context)
- prmpt = nemubot.prompt.hotswap(prmpt)
- print("\033[1;32mContext reloaded\033[0m, now in Nemubot %s" %
- nemubot.__version__)
- except:
- logger.exception("\033[1;31mUnable to reload the prompt due to "
- "errors.\033[0m Fix them before trying to reload "
- "the prompt.")
+ def sighuphandler(signum, frame):
+ """On SIGHUP, perform a deep reload"""
+ import imp
+ nonlocal nemubot, context, module_finder
- context.quit()
- print("Waiting for other threads shuts down...")
+ logger.debug("SIGHUP receive, iniate reload procedure...")
+
+ # Reload nemubot Python modules
+ imp.reload(nemubot)
+ nemubot.reload()
+
+ # Hotswap context
+ import nemubot.bot
+ context = nemubot.bot.hotswap(context)
+
+ # Reload ModuleFinder
+ sys.meta_path.remove(module_finder)
+ module_finder = ModuleFinder(context.modules_paths, context.add_module)
+ sys.meta_path.append(module_finder)
+
+ # Reload configuration file
+ for path in args.files:
+ if os.path.isfile(path):
+ context.sync_queue.put_nowait(["loadconf", path])
+ signal.signal(signal.SIGHUP, sighuphandler)
+
+ def sigusr1handler(signum, frame):
+ """On SIGHUSR1, display stacktraces"""
+ import traceback
+ for threadId, stack in sys._current_frames().items():
+ logger.debug("########### Thread %d:\n%s",
+ threadId,
+ "".join(traceback.format_stack(stack)))
+ signal.signal(signal.SIGUSR1, sigusr1handler)
+
+ if args.socketfile:
+ from nemubot.server.socket import SocketListener
+ context.add_server(SocketListener(context.add_server, "master_socket",
+ sock_location=args.socketfile))
+
+ # context can change when performing an hotswap, always join the latest context
+ oldcontext = None
+ while oldcontext != context:
+ oldcontext = context
+ context.start()
+ context.join()
+
+ # Wait for consumers
+ logger.info("Waiting for other threads shuts down...")
sys.exit(0)
if __name__ == "__main__":
diff --git a/nemubot/bot.py b/nemubot/bot.py
index db2f653..a12dd7a 100644
--- a/nemubot/bot.py
+++ b/nemubot/bot.py
@@ -143,6 +143,7 @@ class Bot(threading.Thread):
from select import select
from nemubot.server import _lock, _rlist, _wlist, _xlist
+ logger.info("Starting main loop")
self.stop = False
while not self.stop:
with _lock:
@@ -208,6 +209,7 @@ class Bot(threading.Thread):
from nemubot.tools.config import load_file
load_file(path, self)
self.sync_queue.task_done()
+ logger.info("Ending main loop")
@@ -509,14 +511,16 @@ def hotswap(bak):
bak.stop = True
if bak.event_timer is not None:
bak.event_timer.cancel()
+
+ # Unload modules
+ for mod in [k for k in bak.modules.keys()]:
+ bak.unload_module(mod)
+
+ # Save datastore
bak.datastore.close()
new = Bot(str(bak.ip), bak.modules_paths, bak.datastore)
new.servers = bak.servers
- new.modules = bak.modules
- new.modules_configuration = bak.modules_configuration
- new.events = bak.events
- new.hooks = bak.hooks
new._update_event_timer()
return new
diff --git a/nemubot/event/__init__.py b/nemubot/event/__init__.py
index 96f226a..345200d 100644
--- a/nemubot/event/__init__.py
+++ b/nemubot/event/__init__.py
@@ -24,8 +24,8 @@ class ModuleEvent:
"""Representation of a event initiated by a bot module"""
def __init__(self, call=None, call_data=None, func=None, func_data=None,
- cmp=None, cmp_data=None, interval=60, offset=0, times=1):
-
+ cmp=None, cmp_data=None, end_call=None, end_data=None,
+ interval=60, offset=0, times=1, max_attempt=-1):
"""Initialize the event
Keyword arguments:
@@ -35,9 +35,12 @@ class ModuleEvent:
func_data -- Argument(s) (single or dict) to pass as argument OR if no func, initial data to watch
cmp -- Boolean function called to check changes
cmp_data -- Argument(s) (single or dict) to pass as argument OR if no cmp, data compared to previous
+ end_call -- Function called when times or max_attempt reach 0 (mainly for interaction with the event manager)
+ end_data -- Argument(s) (single or dict) to pass as argument
interval -- Time in seconds between each check (default: 60)
offset -- Time in seconds added to interval before the first check (default: 0)
times -- Number of times the event has to be realized before being removed; -1 for no limit (default: 1)
+ max_attempt -- Maximum number of times the event will be checked
"""
# What have we to check?
@@ -46,16 +49,17 @@ class ModuleEvent:
# How detect a change?
self.cmp = cmp
- self.cmp_data = None
if cmp_data is not None:
self.cmp_data = cmp_data
- elif self.func is not None:
+ elif callable(self.func):
if self.func_data is None:
self.cmp_data = self.func()
elif isinstance(self.func_data, dict):
self.cmp_data = self.func(**self.func_data)
else:
self.cmp_data = self.func(self.func_data)
+ else:
+ self.cmp_data = None
# What should we call when?
self.call = call
@@ -64,46 +68,29 @@ class ModuleEvent:
else:
self.call_data = func_data
- # Store times
- self.offset = timedelta(seconds=offset) # Time to wait before the first check
+ # Store time between each event
self.interval = timedelta(seconds=interval)
- self._end = None # Cache
-
# How many times do this event?
self.times = times
- @property
- def current(self):
- """Return the date of the near check"""
- if self.times != 0:
- if self._end is None:
- self._end = datetime.now(timezone.utc) + self.offset + self.interval
- return self._end
- return None
+ # Cache the time of the next occurence
+ self.next_occur = datetime.now(timezone.utc) + timedelta(seconds=offset) + self.interval
- @property
- def next(self):
- """Return the date of the next check"""
- if self.times != 0:
- if self._end is None:
- return self.current
- elif self._end < datetime.now(timezone.utc):
- self._end += self.interval
- return self._end
- return None
@property
def time_left(self):
"""Return the time left before/after the near check"""
- if self.current is not None:
- return self.current - datetime.now(timezone.utc)
- return 99999 # TODO: 99999 is not a valid time to return
+
+ return self.next_occur - datetime.now(timezone.utc)
+
def check(self):
"""Run a check and realized the event if this is time"""
+ self.max_attempt -= 1
+
# Get initial data
- if self.func is None:
+ if not callable(self.func):
d_init = self.func_data
elif self.func_data is None:
d_init = self.func()
@@ -113,7 +100,7 @@ class ModuleEvent:
d_init = self.func(self.func_data)
# then compare with current data
- if self.cmp is None:
+ if not callable(self.cmp):
if self.cmp_data is None:
rlz = True
else:
@@ -138,3 +125,19 @@ class ModuleEvent:
self.call(d_init, **self.call_data)
else:
self.call(d_init, self.call_data)
+
+ # Is it finished?
+ if self.times == 0 or self.max_attempt == 0:
+ if not callable(self.end_call):
+ pass # TODO: log a WARN here
+ else:
+ if self.end_data is None:
+ self.end_call()
+ elif isinstance(self.end_data, dict):
+ self.end_call(**self.end_data)
+ else:
+ self.end_call(self.end_data)
+
+ # Not finished, ready to next one!
+ else:
+ self.next_occur += self.interval
diff --git a/nemubot/modulecontext.py b/nemubot/modulecontext.py
index 5b47278..0814215 100644
--- a/nemubot/modulecontext.py
+++ b/nemubot/modulecontext.py
@@ -51,7 +51,7 @@ class ModuleContext:
self.config = ModuleState("module")
self.hooks = list()
- self.events = list()
+ self.events = list() # Un eventManager, qui contient une liste globale et un thread global et quelques méthodes statique, mais chaque événement est exécuté dans son propre contexte :)
self.debug = context.verbosity > 0 if context is not None else False
# Define some callbacks
@@ -79,7 +79,9 @@ class ModuleContext:
def subtreat(msg):
yield from context.treater.treat_msg(msg)
def add_event(evt, eid=None):
- return context.add_event(evt, eid, module_src=module)
+ eid = context.add_event(evt, eid, module_src=module)
+ self.events.append(eid)
+ return eid
def del_event(evt):
return context.del_event(evt, module_src=module)
diff --git a/nemubot/prompt/__init__.py b/nemubot/prompt/__init__.py
deleted file mode 100644
index c491d99..0000000
--- a/nemubot/prompt/__init__.py
+++ /dev/null
@@ -1,144 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Nemubot is a smart and modulable IM bot.
-# Copyright (C) 2012-2015 Mercier Pierre-Olivier
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-import shlex
-import sys
-import traceback
-
-from nemubot.prompt import builtins
-
-
-class Prompt:
-
- def __init__(self):
- self.selectedServer = None
- self.lastretcode = 0
-
- self.HOOKS_CAPS = dict()
- self.HOOKS_LIST = dict()
-
- def add_cap_hook(self, name, call, data=None):
- self.HOOKS_CAPS[name] = lambda t, c: call(t, data=data,
- context=c, prompt=self)
-
- def add_list_hook(self, name, call):
- self.HOOKS_LIST[name] = call
-
- def lex_cmd(self, line):
- """Return an array of tokens
-
- Argument:
- line -- the line to lex
- """
-
- try:
- cmds = shlex.split(line)
- except:
- exc_type, exc_value, _ = sys.exc_info()
- sys.stderr.write(traceback.format_exception_only(exc_type,
- exc_value)[0])
- return
-
- bgn = 0
-
- # Separate commands (command separator: ;)
- for i in range(0, len(cmds)):
- if cmds[i][-1] == ';':
- if i != bgn:
- yield cmds[bgn:i]
- bgn = i + 1
-
- # Return rest of the command (that not end with a ;)
- if bgn != len(cmds):
- yield cmds[bgn:]
-
- def exec_cmd(self, toks, context):
- """Execute the command
-
- Arguments:
- toks -- lexed tokens to executes
- context -- current bot context
- """
-
- if toks[0] in builtins.CAPS:
- self.lastretcode = builtins.CAPS[toks[0]](toks, context, self)
- elif toks[0] in self.HOOKS_CAPS:
- self.lastretcode = self.HOOKS_CAPS[toks[0]](toks, context)
- else:
- print("Unknown command: `%s'" % toks[0])
- self.lastretcode = 127
-
- def getPS1(self):
- """Get the PS1 associated to the selected server"""
- if self.selectedServer is None:
- return "nemubot"
- else:
- return self.selectedServer.id
-
- def run(self, context):
- """Launch the prompt
-
- Argument:
- context -- current bot context
- """
-
- from nemubot.prompt.error import PromptError
- from nemubot.prompt.reset import PromptReset
-
- while True: # Stopped by exception
- try:
- line = input("\033[0;33m%s\033[0;%dm§\033[0m " %
- (self.getPS1(), 31 if self.lastretcode else 32))
- cmds = self.lex_cmd(line.strip())
- for toks in cmds:
- try:
- self.exec_cmd(toks, context)
- except PromptReset:
- raise
- except PromptError as e:
- print(e.message)
- self.lastretcode = 128
- except:
- exc_type, exc_value, exc_traceback = sys.exc_info()
- traceback.print_exception(exc_type, exc_value,
- exc_traceback)
- except KeyboardInterrupt:
- print("")
- except EOFError:
- print("quit")
- return True
-
-
-def hotswap(bak):
- p = Prompt()
- p.HOOKS_CAPS = bak.HOOKS_CAPS
- p.HOOKS_LIST = bak.HOOKS_LIST
- return p
-
-
-def reload():
- import imp
-
- import nemubot.prompt.builtins
- imp.reload(nemubot.prompt.builtins)
-
- import nemubot.prompt.error
- imp.reload(nemubot.prompt.error)
-
- import nemubot.prompt.reset
- imp.reload(nemubot.prompt.reset)
diff --git a/nemubot/prompt/builtins.py b/nemubot/prompt/builtins.py
deleted file mode 100644
index e78c3dd..0000000
--- a/nemubot/prompt/builtins.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Nemubot is a smart and modulable IM bot.
-# Copyright (C) 2012-2015 Mercier Pierre-Olivier
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-def end(toks, context, prompt):
- """Quit the prompt for reload or exit"""
- from nemubot.prompt.reset import PromptReset
-
- if toks[0] == "refresh":
- raise PromptReset("refresh")
- elif toks[0] == "reset":
- raise PromptReset("reset")
- raise PromptReset("quit")
-
-
-def liste(toks, context, prompt):
- """Show some lists"""
- if len(toks) > 1:
- for l in toks[1:]:
- l = l.lower()
- if l == "server" or l == "servers":
- for srv in context.servers.keys():
- print (" - %s (state: %s) ;" % (srv,
- "connected" if context.servers[srv].connected else "disconnected"))
- if len(context.servers) == 0:
- print (" > No server loaded")
-
- elif l == "mod" or l == "mods" or l == "module" or l == "modules":
- for mod in context.modules.keys():
- print (" - %s ;" % mod)
- if len(context.modules) == 0:
- print (" > No module loaded")
-
- elif l in prompt.HOOKS_LIST:
- f, d = prompt.HOOKS_LIST[l]
- f(d, context, prompt)
-
- else:
- print (" Unknown list `%s'" % l)
- return 2
- return 0
- else:
- print (" Please give a list to show: servers, ...")
- return 1
-
-
-def load(toks, context, prompt):
- """Load an XML configuration file"""
- if len(toks) > 1:
- from nemubot.tools.config import load_file
-
- for filename in toks[1:]:
- load_file(filename, context)
- else:
- print ("Not enough arguments. `load' takes a filename.")
- return 1
-
-
-def select(toks, context, prompt):
- """Select the current server"""
- if (len(toks) == 2 and toks[1] != "None" and
- toks[1] != "nemubot" and toks[1] != "none"):
- if toks[1] in context.servers:
- prompt.selectedServer = context.servers[toks[1]]
- else:
- print ("select: server `%s' not found." % toks[1])
- return 1
- else:
- prompt.selectedServer = None
-
-
-def unload(toks, context, prompt):
- """Unload a module"""
- if len(toks) == 2 and toks[1] == "all":
- for name in context.modules.keys():
- context.unload_module(name)
- elif len(toks) > 1:
- for name in toks[1:]:
- if context.unload_module(name):
- print (" Module `%s' successfully unloaded." % name)
- else:
- print (" No module `%s' loaded, can't unload!" % name)
- return 2
- else:
- print ("Not enough arguments. `unload' takes a module name.")
- return 1
-
-
-def debug(toks, context, prompt):
- """Enable/Disable debug mode on a module"""
- if len(toks) > 1:
- for name in toks[1:]:
- if name in context.modules:
- context.modules[name].DEBUG = not context.modules[name].DEBUG
- if context.modules[name].DEBUG:
- print (" Module `%s' now in DEBUG mode." % name)
- else:
- print (" Debug for module module `%s' disabled." % name)
- else:
- print (" No module `%s' loaded, can't debug!" % name)
- return 2
- else:
- print ("Not enough arguments. `debug' takes a module name.")
- return 1
-
-
-# Register build-ins
-CAPS = {
- 'quit': end, # Disconnect all server and quit
- 'exit': end, # Alias for quit
- 'reset': end, # Reload the prompt
- 'refresh': end, # Reload the prompt but save modules
- 'load': load, # Load a servers or module configuration file
- 'unload': unload, # Unload a module and remove it from the list
- 'select': select, # Select a server
- 'list': liste, # Show lists
- 'debug': debug, # Pass a module in debug mode
-}
diff --git a/nemubot/prompt/error.py b/nemubot/prompt/error.py
deleted file mode 100644
index 3d426d6..0000000
--- a/nemubot/prompt/error.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# coding=utf-8
-
-# Nemubot is a smart and modulable IM bot.
-# Copyright (C) 2012-2015 Mercier Pierre-Olivier
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-class PromptError(Exception):
-
- def __init__(self, message):
- super(PromptError, self).__init__(message)
- self.message = message
diff --git a/nemubot/prompt/reset.py b/nemubot/prompt/reset.py
deleted file mode 100644
index 57da9f8..0000000
--- a/nemubot/prompt/reset.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# coding=utf-8
-
-# Nemubot is a smart and modulable IM bot.
-# Copyright (C) 2012-2015 Mercier Pierre-Olivier
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-
-class PromptReset(Exception):
-
- def __init__(self, type):
- super(PromptReset, self).__init__("Prompt reset asked")
- self.type = type
diff --git a/nemubot/server/abstract.py b/nemubot/server/abstract.py
index 99d10d5..b3c70ca 100644
--- a/nemubot/server/abstract.py
+++ b/nemubot/server/abstract.py
@@ -115,7 +115,7 @@ class AbstractServer(io.IOBase):
"""
self._sending_queue.put(self.format(message))
- self.logger.debug("Message '%s' appended to Queue", message)
+ self.logger.debug("Message '%s' appended to write queue", message)
if self not in _wlist:
_wlist.append(self)
diff --git a/nemubot/server/socket.py b/nemubot/server/socket.py
index 052579b..f810906 100644
--- a/nemubot/server/socket.py
+++ b/nemubot/server/socket.py
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+import nemubot.message as message
from nemubot.message.printer.socket import Socket as SocketPrinter
from nemubot.server.abstract import AbstractServer
@@ -132,6 +133,18 @@ class SocketServer(AbstractServer):
yield line
+ def parse(self, line):
+ import shlex
+
+ line = line.strip().decode()
+ try:
+ args = shlex.split(line)
+ except ValueError:
+ args = line.split(' ')
+
+ yield message.Command(cmd=args[0], args=args[1:], server=self)
+
+
class SocketListener(AbstractServer):
def __init__(self, new_server_cb, id, sock_location=None, host=None, port=None, ssl=None):
diff --git a/setup.py b/setup.py
index 37f4aef..dc448ca 100755
--- a/setup.py
+++ b/setup.py
@@ -66,7 +66,6 @@ setup(
'nemubot.hooks',
'nemubot.message',
'nemubot.message.printer',
- 'nemubot.prompt',
'nemubot.server',
'nemubot.server.message',
'nemubot.tools',