python: plugin cleanup and refactor
This commit is contained in:
@@ -1,30 +1,30 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import pydoc
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
import importlib
|
||||
import signal
|
||||
import site
|
||||
import sys
|
||||
import traceback
|
||||
import weakref
|
||||
from contextlib import contextmanager
|
||||
|
||||
from _hexchat_embedded import ffi, lib
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
from io import BytesIO as HelpEater
|
||||
else:
|
||||
from io import StringIO as HelpEater
|
||||
|
||||
from _hexchat_embedded import ffi, lib
|
||||
|
||||
if not hasattr(sys, 'argv'):
|
||||
sys.argv = ['<hexchat>']
|
||||
|
||||
VERSION = b'2.0' # Sync with hexchat.__version__
|
||||
PLUGIN_NAME = ffi.new('char[]', b'Python')
|
||||
PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface'
|
||||
% (sys.version_info[0], sys.version_info[1]))
|
||||
PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' % (sys.version_info[0], sys.version_info[1]))
|
||||
PLUGIN_VERSION = ffi.new('char[]', VERSION)
|
||||
|
||||
# TODO: Constants should be screaming snake case
|
||||
hexchat = None
|
||||
local_interp = None
|
||||
hexchat_stdout = None
|
||||
@@ -40,10 +40,11 @@ def redirected_stdout():
|
||||
sys.stderr = hexchat_stdout
|
||||
|
||||
|
||||
if os.environ.get('HEXCHAT_LOG_PYTHON'):
|
||||
if os.getenv('HEXCHAT_LOG_PYTHON'):
|
||||
def log(*args):
|
||||
with redirected_stdout():
|
||||
print(*args)
|
||||
|
||||
else:
|
||||
def log(*args):
|
||||
pass
|
||||
@@ -56,7 +57,7 @@ class Stdout:
|
||||
def write(self, string):
|
||||
string = string.encode()
|
||||
idx = string.rfind(b'\n')
|
||||
if idx is not -1:
|
||||
if idx != -1:
|
||||
self.buffer += string[:idx]
|
||||
lib.hexchat_print(lib.ph, bytes(self.buffer))
|
||||
self.buffer = bytearray(string[idx + 1:])
|
||||
@@ -91,13 +92,15 @@ class Hook:
|
||||
lib.hexchat_unhook(lib.ph, self.hexchat_hook)
|
||||
|
||||
|
||||
if sys.version_info[0] is 2:
|
||||
if sys.version_info[0] == 2:
|
||||
def compile_file(data, filename):
|
||||
return compile(data, filename, 'exec', dont_inherit=True)
|
||||
|
||||
|
||||
def compile_line(string):
|
||||
try:
|
||||
return compile(string, '<string>', 'eval', dont_inherit=True)
|
||||
|
||||
except SyntaxError:
|
||||
# For some reason `print` is invalid for eval
|
||||
# This will hide any return value though
|
||||
@@ -106,6 +109,7 @@ else:
|
||||
def compile_file(data, filename):
|
||||
return compile(data, filename, 'exec', optimize=2, dont_inherit=True)
|
||||
|
||||
|
||||
def compile_line(string):
|
||||
# newline appended to solve unexpected EOF issues
|
||||
return compile(string + '\n', '<string>', 'single', optimize=2, dont_inherit=True)
|
||||
@@ -135,8 +139,9 @@ class Plugin:
|
||||
ud = h.userdata
|
||||
self.hooks.remove(h)
|
||||
return ud
|
||||
else:
|
||||
log('Hook not found')
|
||||
|
||||
log('Hook not found')
|
||||
return None
|
||||
|
||||
def loadfile(self, filename):
|
||||
try:
|
||||
@@ -148,21 +153,22 @@ class Plugin:
|
||||
|
||||
try:
|
||||
self.name = self.globals['__module_name__']
|
||||
|
||||
except KeyError:
|
||||
lib.hexchat_print(lib.ph, b'Failed to load module: __module_name__ must be set')
|
||||
|
||||
return False
|
||||
|
||||
self.version = self.globals.get('__module_version__', '')
|
||||
self.description = self.globals.get('__module_description__', '')
|
||||
self.ph = lib.hexchat_plugingui_add(lib.ph, filename.encode(),
|
||||
self.name.encode(),
|
||||
self.description.encode(),
|
||||
self.version.encode(),
|
||||
ffi.NULL)
|
||||
self.ph = lib.hexchat_plugingui_add(lib.ph, filename.encode(), self.name.encode(),
|
||||
self.description.encode(), self.version.encode(), ffi.NULL)
|
||||
|
||||
except Exception as e:
|
||||
lib.hexchat_print(lib.ph, 'Failed to load module: {}'.format(e).encode())
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __del__(self):
|
||||
@@ -171,17 +177,20 @@ class Plugin:
|
||||
if hook.is_unload is True:
|
||||
try:
|
||||
hook.callback(hook.userdata)
|
||||
|
||||
except Exception as e:
|
||||
log('Failed to run hook:', e)
|
||||
traceback.print_exc()
|
||||
|
||||
del self.hooks
|
||||
if self.ph is not None:
|
||||
lib.hexchat_plugingui_remove(lib.ph, self.ph)
|
||||
|
||||
|
||||
if sys.version_info[0] is 2:
|
||||
if sys.version_info[0] == 2:
|
||||
def __decode(string):
|
||||
return string
|
||||
|
||||
else:
|
||||
def __decode(string):
|
||||
return string.decode()
|
||||
@@ -192,6 +201,7 @@ def wordlist_len(words):
|
||||
for i in range(31, 1, -1):
|
||||
if ffi.string(words[i]):
|
||||
return i
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -205,24 +215,26 @@ def create_wordlist(words):
|
||||
# This makes no sense to do...
|
||||
def create_wordeollist(words):
|
||||
words = reversed(words)
|
||||
last = None
|
||||
accum = None
|
||||
ret = []
|
||||
for word in words:
|
||||
if accum is None:
|
||||
accum = word
|
||||
|
||||
elif word:
|
||||
last = accum
|
||||
accum = ' '.join((word, last))
|
||||
|
||||
ret.insert(0, accum)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def to_cb_ret(value):
|
||||
if value is None:
|
||||
return 0
|
||||
else:
|
||||
return int(value)
|
||||
|
||||
return int(value)
|
||||
|
||||
|
||||
@ffi.def_extern()
|
||||
@@ -274,13 +286,14 @@ def _on_timer_hook(userdata):
|
||||
hook = ffi.from_handle(userdata)
|
||||
if hook.callback(hook.userdata) is True:
|
||||
return 1
|
||||
else:
|
||||
hook.is_unload = True # Don't unhook
|
||||
for h in hook.plugin.hooks:
|
||||
if h == hook:
|
||||
hook.plugin.hooks.remove(h)
|
||||
break
|
||||
return 0
|
||||
|
||||
hook.is_unload = True # Don't unhook
|
||||
for h in hook.plugin.hooks:
|
||||
if h == hook:
|
||||
hook.plugin.hooks.remove(h)
|
||||
break
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@ffi.def_extern(error=3)
|
||||
@@ -291,6 +304,7 @@ def _on_say_command(word, word_eol, userdata):
|
||||
lib.hexchat_print(lib.ph, b'>>> ' + python)
|
||||
exec_in_interp(__decode(python))
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -298,33 +312,36 @@ def load_filename(filename):
|
||||
filename = os.path.expanduser(filename)
|
||||
if not os.path.isabs(filename):
|
||||
configdir = __decode(ffi.string(lib.hexchat_get_info(lib.ph, b'configdir')))
|
||||
|
||||
filename = os.path.join(configdir, 'addons', filename)
|
||||
|
||||
if filename and not any(plugin.filename == filename for plugin in plugins):
|
||||
plugin = Plugin()
|
||||
if plugin.loadfile(filename):
|
||||
plugins.add(plugin)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def unload_name(name):
|
||||
if name:
|
||||
for plugin in plugins:
|
||||
if name in (plugin.name, plugin.filename,
|
||||
os.path.basename(plugin.filename)):
|
||||
if name in (plugin.name, plugin.filename, os.path.basename(plugin.filename)):
|
||||
plugins.remove(plugin)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def reload_name(name):
|
||||
if name:
|
||||
for plugin in plugins:
|
||||
if name in (plugin.name, plugin.filename,
|
||||
os.path.basename(plugin.filename)):
|
||||
if name in (plugin.name, plugin.filename, os.path.basename(plugin.filename)):
|
||||
filename = plugin.filename
|
||||
plugins.remove(plugin)
|
||||
return load_filename(filename)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@@ -346,6 +363,7 @@ def autoload():
|
||||
log('Autoloading', f)
|
||||
# TODO: Set cwd
|
||||
load_filename(os.path.join(addondir, f))
|
||||
|
||||
except FileNotFoundError as e:
|
||||
log('Autoload failed', e)
|
||||
|
||||
@@ -376,7 +394,6 @@ def list_plugins():
|
||||
for row in tbl:
|
||||
lib.hexchat_print(lib.ph, b' '.join(item.ljust(column_sizes[i])
|
||||
for i, item in enumerate(row)))
|
||||
|
||||
lib.hexchat_print(lib.ph, b'')
|
||||
|
||||
|
||||
@@ -396,6 +413,7 @@ def exec_in_interp(python):
|
||||
ret = eval(code, local_interp.globals, local_interp.locals)
|
||||
if ret is not None:
|
||||
lib.hexchat_print(lib.ph, '{}'.format(ret).encode())
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc(file=hexchat_stdout)
|
||||
|
||||
@@ -406,6 +424,7 @@ def _on_load_command(word, word_eol, userdata):
|
||||
if filename.endswith(b'.py'):
|
||||
load_filename(__decode(filename))
|
||||
return 3
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -415,6 +434,7 @@ def _on_unload_command(word, word_eol, userdata):
|
||||
if filename.endswith(b'.py'):
|
||||
unload_name(__decode(filename))
|
||||
return 3
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -424,6 +444,7 @@ def _on_reload_command(word, word_eol, userdata):
|
||||
if filename.endswith(b'.py'):
|
||||
reload_name(__decode(filename))
|
||||
return 3
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -434,23 +455,30 @@ def _on_py_command(word, word_eol, userdata):
|
||||
if subcmd == 'exec':
|
||||
python = __decode(ffi.string(word_eol[3]))
|
||||
exec_in_interp(python)
|
||||
|
||||
elif subcmd == 'load':
|
||||
filename = __decode(ffi.string(word[3]))
|
||||
load_filename(filename)
|
||||
|
||||
elif subcmd == 'unload':
|
||||
name = __decode(ffi.string(word[3]))
|
||||
if not unload_name(name):
|
||||
lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name')
|
||||
|
||||
elif subcmd == 'reload':
|
||||
name = __decode(ffi.string(word[3]))
|
||||
if not reload_name(name):
|
||||
lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name')
|
||||
|
||||
elif subcmd == 'console':
|
||||
lib.hexchat_command(lib.ph, b'QUERY >>python<<')
|
||||
|
||||
elif subcmd == 'list':
|
||||
list_plugins()
|
||||
|
||||
elif subcmd == 'about':
|
||||
lib.hexchat_print(lib.ph, b'HexChat Python interface version ' + VERSION)
|
||||
|
||||
else:
|
||||
lib.hexchat_command(lib.ph, b'HELP PY')
|
||||
|
||||
@@ -473,8 +501,10 @@ def _on_plugin_init(plugin_name, plugin_desc, plugin_version, arg, libdir):
|
||||
modpath = os.path.join(libdir, '..', 'python')
|
||||
sys.path.append(os.path.abspath(modpath))
|
||||
hexchat = importlib.import_module('hexchat')
|
||||
|
||||
except (UnicodeDecodeError, ImportError) as e:
|
||||
lib.hexchat_print(lib.ph, b'Failed to import module: ' + repr(e).encode())
|
||||
|
||||
return 0
|
||||
|
||||
hexchat_stdout = Stdout()
|
||||
@@ -517,6 +547,7 @@ def _on_plugin_deinit():
|
||||
for mod in ('_hexchat', 'hexchat', 'xchat', '_hexchat_embedded'):
|
||||
try:
|
||||
del sys.modules[mod]
|
||||
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user