python: plugin cleanup and refactor
This commit is contained in:
parent
a5a727122b
commit
7abeb10cf1
@ -1,6 +1,7 @@
|
|||||||
from contextlib import contextmanager
|
|
||||||
import inspect
|
import inspect
|
||||||
import sys
|
import sys
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
from _hexchat_embedded import ffi, lib
|
from _hexchat_embedded import ffi, lib
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@ -40,13 +41,15 @@ def __get_current_plugin():
|
|||||||
while '__plugin' not in frame.f_globals:
|
while '__plugin' not in frame.f_globals:
|
||||||
frame = frame.f_back
|
frame = frame.f_back
|
||||||
assert frame is not None
|
assert frame is not None
|
||||||
|
|
||||||
return frame.f_globals['__plugin']
|
return frame.f_globals['__plugin']
|
||||||
|
|
||||||
|
|
||||||
# Keeping API compat
|
# Keeping API compat
|
||||||
if sys.version_info[0] is 2:
|
if sys.version_info[0] == 2:
|
||||||
def __decode(string):
|
def __decode(string):
|
||||||
return string
|
return string
|
||||||
|
|
||||||
else:
|
else:
|
||||||
def __decode(string):
|
def __decode(string):
|
||||||
return string.decode()
|
return string.decode()
|
||||||
@ -64,16 +67,18 @@ def emit_print(event_name, *args, **kwargs):
|
|||||||
arg = args[i].encode() if len(args) > i else b''
|
arg = args[i].encode() if len(args) > i else b''
|
||||||
cstring = ffi.new('char[]', arg)
|
cstring = ffi.new('char[]', arg)
|
||||||
cargs.append(cstring)
|
cargs.append(cstring)
|
||||||
if time is 0:
|
|
||||||
|
if time == 0:
|
||||||
return lib.hexchat_emit_print(lib.ph, event_name.encode(), *cargs)
|
return lib.hexchat_emit_print(lib.ph, event_name.encode(), *cargs)
|
||||||
else:
|
|
||||||
attrs = lib.hexchat_event_attrs_create(lib.ph)
|
attrs = lib.hexchat_event_attrs_create(lib.ph)
|
||||||
attrs.server_time_utc = time
|
attrs.server_time_utc = time
|
||||||
ret = lib.hexchat_emit_print(lib.ph, attrs, event_name.encode(), *cargs)
|
ret = lib.hexchat_emit_print(lib.ph, attrs, event_name.encode(), *cargs)
|
||||||
lib.hexchat_event_attrs_free(lib.ph, attrs)
|
lib.hexchat_event_attrs_free(lib.ph, attrs)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: this shadows itself. command should be changed to cmd
|
||||||
def command(command):
|
def command(command):
|
||||||
lib.hexchat_command(lib.ph, command.encode())
|
lib.hexchat_command(lib.ph, command.encode())
|
||||||
|
|
||||||
@ -97,21 +102,24 @@ def get_info(name):
|
|||||||
# Surely there is a less dumb way?
|
# Surely there is a less dumb way?
|
||||||
ptr = repr(ret).rsplit(' ', 1)[1][:-1]
|
ptr = repr(ret).rsplit(' ', 1)[1][:-1]
|
||||||
return ptr
|
return ptr
|
||||||
|
|
||||||
return __decode(ffi.string(ret))
|
return __decode(ffi.string(ret))
|
||||||
|
|
||||||
|
|
||||||
def get_prefs(name):
|
def get_prefs(name):
|
||||||
string_out = ffi.new('char**')
|
string_out = ffi.new('char**')
|
||||||
int_out = ffi.new('int*')
|
int_out = ffi.new('int*')
|
||||||
type = lib.hexchat_get_prefs(lib.ph, name.encode(), string_out, int_out)
|
_type = lib.hexchat_get_prefs(lib.ph, name.encode(), string_out, int_out)
|
||||||
if type is 0:
|
if _type == 0:
|
||||||
return None
|
return None
|
||||||
elif type is 1:
|
|
||||||
|
if _type == 1:
|
||||||
return __decode(ffi.string(string_out[0]))
|
return __decode(ffi.string(string_out[0]))
|
||||||
elif type is 2 or type is 3: # XXX: 3 should be a bool, but keeps API
|
|
||||||
|
if _type in (2, 3): # XXX: 3 should be a bool, but keeps API
|
||||||
return int_out[0]
|
return int_out[0]
|
||||||
else:
|
|
||||||
assert False
|
raise AssertionError('Out of bounds pref storage')
|
||||||
|
|
||||||
|
|
||||||
def __cstrarray_to_list(arr):
|
def __cstrarray_to_list(arr):
|
||||||
@ -120,6 +128,7 @@ def __cstrarray_to_list(arr):
|
|||||||
while arr[i] != ffi.NULL:
|
while arr[i] != ffi.NULL:
|
||||||
ret.append(ffi.string(arr[i]))
|
ret.append(ffi.string(arr[i]))
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
@ -127,8 +136,7 @@ __FIELD_CACHE = {}
|
|||||||
|
|
||||||
|
|
||||||
def __get_fields(name):
|
def __get_fields(name):
|
||||||
return __FIELD_CACHE.setdefault(name,
|
return __FIELD_CACHE.setdefault(name, __cstrarray_to_list(lib.hexchat_list_fields(lib.ph, name)))
|
||||||
__cstrarray_to_list(lib.hexchat_list_fields(lib.ph, name)))
|
|
||||||
|
|
||||||
|
|
||||||
__FIELD_PROPERTY_CACHE = {}
|
__FIELD_PROPERTY_CACHE = {}
|
||||||
@ -154,6 +162,7 @@ class ListItem:
|
|||||||
if sys.version_info[0] == 2:
|
if sys.version_info[0] == 2:
|
||||||
def get_getter(name):
|
def get_getter(name):
|
||||||
return ord(name[0])
|
return ord(name[0])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
def get_getter(name):
|
def get_getter(name):
|
||||||
return name[0]
|
return name[0]
|
||||||
@ -179,6 +188,7 @@ def get_list(name):
|
|||||||
string = lib.hexchat_list_str(lib.ph, list_, field)
|
string = lib.hexchat_list_str(lib.ph, list_, field)
|
||||||
if string != ffi.NULL:
|
if string != ffi.NULL:
|
||||||
return __decode(ffi.string(string))
|
return __decode(ffi.string(string))
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def ptr_getter(field):
|
def ptr_getter(field):
|
||||||
@ -186,6 +196,7 @@ def get_list(name):
|
|||||||
ptr = lib.hexchat_list_str(lib.ph, list_, field)
|
ptr = lib.hexchat_list_str(lib.ph, list_, field)
|
||||||
ctx = ffi.cast('hexchat_context*', ptr)
|
ctx = ffi.cast('hexchat_context*', ptr)
|
||||||
return Context(ctx)
|
return Context(ctx)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
getters = {
|
getters = {
|
||||||
@ -195,25 +206,27 @@ def get_list(name):
|
|||||||
ord('p'): ptr_getter,
|
ord('p'): ptr_getter,
|
||||||
}
|
}
|
||||||
|
|
||||||
while lib.hexchat_list_next(lib.ph, list_) is 1:
|
while lib.hexchat_list_next(lib.ph, list_) == 1:
|
||||||
item = ListItem(orig_name)
|
item = ListItem(orig_name)
|
||||||
for field in fields:
|
for _field in fields:
|
||||||
getter = getters.get(get_getter(field))
|
getter = getters.get(get_getter(_field))
|
||||||
if getter is not None:
|
if getter is not None:
|
||||||
field_name = field[1:]
|
field_name = _field[1:]
|
||||||
setattr(item, __cached_decoded_str(field_name), getter(field_name))
|
setattr(item, __cached_decoded_str(field_name), getter(field_name))
|
||||||
|
|
||||||
ret.append(item)
|
ret.append(item)
|
||||||
|
|
||||||
lib.hexchat_list_free(lib.ph, list_)
|
lib.hexchat_list_free(lib.ph, list_)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: 'command' here shadows command above, and should be renamed to cmd
|
||||||
def hook_command(command, callback, userdata=None, priority=PRI_NORM, help=None):
|
def hook_command(command, callback, userdata=None, priority=PRI_NORM, help=None):
|
||||||
plugin = __get_current_plugin()
|
plugin = __get_current_plugin()
|
||||||
hook = plugin.add_hook(callback, userdata)
|
hook = plugin.add_hook(callback, userdata)
|
||||||
handle = lib.hexchat_hook_command(lib.ph, command.encode(), priority, lib._on_command_hook,
|
handle = lib.hexchat_hook_command(lib.ph, command.encode(), priority, lib._on_command_hook,
|
||||||
help.encode() if help is not None else ffi.NULL,
|
help.encode() if help is not None else ffi.NULL, hook.handle)
|
||||||
hook.handle)
|
|
||||||
hook.hexchat_hook = handle
|
hook.hexchat_hook = handle
|
||||||
return id(hook)
|
return id(hook)
|
||||||
|
|
||||||
@ -221,8 +234,7 @@ def hook_command(command, callback, userdata=None, priority=PRI_NORM, help=None)
|
|||||||
def hook_print(name, callback, userdata=None, priority=PRI_NORM):
|
def hook_print(name, callback, userdata=None, priority=PRI_NORM):
|
||||||
plugin = __get_current_plugin()
|
plugin = __get_current_plugin()
|
||||||
hook = plugin.add_hook(callback, userdata)
|
hook = plugin.add_hook(callback, userdata)
|
||||||
handle = lib.hexchat_hook_print(lib.ph, name.encode(), priority, lib._on_print_hook,
|
handle = lib.hexchat_hook_print(lib.ph, name.encode(), priority, lib._on_print_hook, hook.handle)
|
||||||
hook.handle)
|
|
||||||
hook.hexchat_hook = handle
|
hook.hexchat_hook = handle
|
||||||
return id(hook)
|
return id(hook)
|
||||||
|
|
||||||
@ -230,8 +242,7 @@ def hook_print(name, callback, userdata=None, priority=PRI_NORM):
|
|||||||
def hook_print_attrs(name, callback, userdata=None, priority=PRI_NORM):
|
def hook_print_attrs(name, callback, userdata=None, priority=PRI_NORM):
|
||||||
plugin = __get_current_plugin()
|
plugin = __get_current_plugin()
|
||||||
hook = plugin.add_hook(callback, userdata)
|
hook = plugin.add_hook(callback, userdata)
|
||||||
handle = lib.hexchat_hook_print_attrs(lib.ph, name.encode(), priority, lib._on_print_attrs_hook,
|
handle = lib.hexchat_hook_print_attrs(lib.ph, name.encode(), priority, lib._on_print_attrs_hook, hook.handle)
|
||||||
hook.handle)
|
|
||||||
hook.hexchat_hook = handle
|
hook.hexchat_hook = handle
|
||||||
return id(hook)
|
return id(hook)
|
||||||
|
|
||||||
@ -239,8 +250,7 @@ def hook_print_attrs(name, callback, userdata=None, priority=PRI_NORM):
|
|||||||
def hook_server(name, callback, userdata=None, priority=PRI_NORM):
|
def hook_server(name, callback, userdata=None, priority=PRI_NORM):
|
||||||
plugin = __get_current_plugin()
|
plugin = __get_current_plugin()
|
||||||
hook = plugin.add_hook(callback, userdata)
|
hook = plugin.add_hook(callback, userdata)
|
||||||
handle = lib.hexchat_hook_server(lib.ph, name.encode(), priority, lib._on_server_hook,
|
handle = lib.hexchat_hook_server(lib.ph, name.encode(), priority, lib._on_server_hook, hook.handle)
|
||||||
hook.handle)
|
|
||||||
hook.hexchat_hook = handle
|
hook.hexchat_hook = handle
|
||||||
return id(hook)
|
return id(hook)
|
||||||
|
|
||||||
@ -248,8 +258,7 @@ def hook_server(name, callback, userdata=None, priority=PRI_NORM):
|
|||||||
def hook_server_attrs(name, callback, userdata=None, priority=PRI_NORM):
|
def hook_server_attrs(name, callback, userdata=None, priority=PRI_NORM):
|
||||||
plugin = __get_current_plugin()
|
plugin = __get_current_plugin()
|
||||||
hook = plugin.add_hook(callback, userdata)
|
hook = plugin.add_hook(callback, userdata)
|
||||||
handle = lib.hexchat_hook_server_attrs(lib.ph, name.encode(), priority, lib._on_server_attrs_hook,
|
handle = lib.hexchat_hook_server_attrs(lib.ph, name.encode(), priority, lib._on_server_attrs_hook, hook.handle)
|
||||||
hook.handle)
|
|
||||||
hook.hexchat_hook = handle
|
hook.hexchat_hook = handle
|
||||||
return id(hook)
|
return id(hook)
|
||||||
|
|
||||||
@ -276,17 +285,18 @@ def unhook(handle):
|
|||||||
def set_pluginpref(name, value):
|
def set_pluginpref(name, value):
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
return bool(lib.hexchat_pluginpref_set_str(lib.ph, name.encode(), value.encode()))
|
return bool(lib.hexchat_pluginpref_set_str(lib.ph, name.encode(), value.encode()))
|
||||||
elif isinstance(value, int):
|
|
||||||
|
if isinstance(value, int):
|
||||||
return bool(lib.hexchat_pluginpref_set_int(lib.ph, name.encode(), value))
|
return bool(lib.hexchat_pluginpref_set_int(lib.ph, name.encode(), value))
|
||||||
else:
|
|
||||||
# XXX: This should probably raise but this keeps API
|
# XXX: This should probably raise but this keeps API
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_pluginpref(name):
|
def get_pluginpref(name):
|
||||||
name = name.encode()
|
name = name.encode()
|
||||||
string_out = ffi.new('char[512]')
|
string_out = ffi.new('char[512]')
|
||||||
if lib.hexchat_pluginpref_get_str(lib.ph, name, string_out) is not 1:
|
if lib.hexchat_pluginpref_get_str(lib.ph, name, string_out) != 1:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
string = ffi.string(string_out)
|
string = ffi.string(string_out)
|
||||||
@ -308,8 +318,9 @@ def del_pluginpref(name):
|
|||||||
|
|
||||||
def list_pluginpref():
|
def list_pluginpref():
|
||||||
prefs_str = ffi.new('char[4096]')
|
prefs_str = ffi.new('char[4096]')
|
||||||
if lib.hexchat_pluginpref_list(lib.ph, prefs_str) is 1:
|
if lib.hexchat_pluginpref_list(lib.ph, prefs_str) == 1:
|
||||||
return __decode(prefs_str).split(',')
|
return __decode(prefs_str).split(',')
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
@ -320,6 +331,7 @@ class Context:
|
|||||||
def __eq__(self, value):
|
def __eq__(self, value):
|
||||||
if not isinstance(value, Context):
|
if not isinstance(value, Context):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return self._ctx == value._ctx
|
return self._ctx == value._ctx
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
@ -327,9 +339,9 @@ class Context:
|
|||||||
old_ctx = lib.hexchat_get_context(lib.ph)
|
old_ctx = lib.hexchat_get_context(lib.ph)
|
||||||
if not self.set():
|
if not self.set():
|
||||||
# XXX: Behavior change, previously used wrong context
|
# XXX: Behavior change, previously used wrong context
|
||||||
lib.hexchat_print(lib.ph,
|
lib.hexchat_print(lib.ph, b'Context object refers to closed context, ignoring call')
|
||||||
b'Context object refers to closed context, ignoring call')
|
|
||||||
return
|
return
|
||||||
|
|
||||||
yield
|
yield
|
||||||
lib.hexchat_set_context(lib.ph, old_ctx)
|
lib.hexchat_set_context(lib.ph, old_ctx)
|
||||||
|
|
||||||
@ -370,4 +382,5 @@ def find_context(server=None, channel=None):
|
|||||||
ctx = lib.hexchat_find_context(lib.ph, server, channel)
|
ctx = lib.hexchat_find_context(lib.ph, server, channel)
|
||||||
if ctx == ffi.NULL:
|
if ctx == ffi.NULL:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return Context(ctx)
|
return Context(ctx)
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import importlib
|
||||||
import os
|
import os
|
||||||
import pydoc
|
import pydoc
|
||||||
import sys
|
|
||||||
from contextlib import contextmanager
|
|
||||||
import importlib
|
|
||||||
import signal
|
import signal
|
||||||
import site
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
import weakref
|
import weakref
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from _hexchat_embedded import ffi, lib
|
||||||
|
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
from io import BytesIO as HelpEater
|
from io import BytesIO as HelpEater
|
||||||
else:
|
else:
|
||||||
from io import StringIO as HelpEater
|
from io import StringIO as HelpEater
|
||||||
|
|
||||||
from _hexchat_embedded import ffi, lib
|
|
||||||
|
|
||||||
if not hasattr(sys, 'argv'):
|
if not hasattr(sys, 'argv'):
|
||||||
sys.argv = ['<hexchat>']
|
sys.argv = ['<hexchat>']
|
||||||
|
|
||||||
VERSION = b'2.0' # Sync with hexchat.__version__
|
VERSION = b'2.0' # Sync with hexchat.__version__
|
||||||
PLUGIN_NAME = ffi.new('char[]', b'Python')
|
PLUGIN_NAME = ffi.new('char[]', b'Python')
|
||||||
PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface'
|
PLUGIN_DESC = ffi.new('char[]', b'Python %d.%d scripting interface' % (sys.version_info[0], sys.version_info[1]))
|
||||||
% (sys.version_info[0], sys.version_info[1]))
|
|
||||||
PLUGIN_VERSION = ffi.new('char[]', VERSION)
|
PLUGIN_VERSION = ffi.new('char[]', VERSION)
|
||||||
|
|
||||||
|
# TODO: Constants should be screaming snake case
|
||||||
hexchat = None
|
hexchat = None
|
||||||
local_interp = None
|
local_interp = None
|
||||||
hexchat_stdout = None
|
hexchat_stdout = None
|
||||||
@ -40,10 +40,11 @@ def redirected_stdout():
|
|||||||
sys.stderr = hexchat_stdout
|
sys.stderr = hexchat_stdout
|
||||||
|
|
||||||
|
|
||||||
if os.environ.get('HEXCHAT_LOG_PYTHON'):
|
if os.getenv('HEXCHAT_LOG_PYTHON'):
|
||||||
def log(*args):
|
def log(*args):
|
||||||
with redirected_stdout():
|
with redirected_stdout():
|
||||||
print(*args)
|
print(*args)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
def log(*args):
|
def log(*args):
|
||||||
pass
|
pass
|
||||||
@ -56,7 +57,7 @@ class Stdout:
|
|||||||
def write(self, string):
|
def write(self, string):
|
||||||
string = string.encode()
|
string = string.encode()
|
||||||
idx = string.rfind(b'\n')
|
idx = string.rfind(b'\n')
|
||||||
if idx is not -1:
|
if idx != -1:
|
||||||
self.buffer += string[:idx]
|
self.buffer += string[:idx]
|
||||||
lib.hexchat_print(lib.ph, bytes(self.buffer))
|
lib.hexchat_print(lib.ph, bytes(self.buffer))
|
||||||
self.buffer = bytearray(string[idx + 1:])
|
self.buffer = bytearray(string[idx + 1:])
|
||||||
@ -91,13 +92,15 @@ class Hook:
|
|||||||
lib.hexchat_unhook(lib.ph, self.hexchat_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):
|
def compile_file(data, filename):
|
||||||
return compile(data, filename, 'exec', dont_inherit=True)
|
return compile(data, filename, 'exec', dont_inherit=True)
|
||||||
|
|
||||||
|
|
||||||
def compile_line(string):
|
def compile_line(string):
|
||||||
try:
|
try:
|
||||||
return compile(string, '<string>', 'eval', dont_inherit=True)
|
return compile(string, '<string>', 'eval', dont_inherit=True)
|
||||||
|
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
# For some reason `print` is invalid for eval
|
# For some reason `print` is invalid for eval
|
||||||
# This will hide any return value though
|
# This will hide any return value though
|
||||||
@ -106,6 +109,7 @@ else:
|
|||||||
def compile_file(data, filename):
|
def compile_file(data, filename):
|
||||||
return compile(data, filename, 'exec', optimize=2, dont_inherit=True)
|
return compile(data, filename, 'exec', optimize=2, dont_inherit=True)
|
||||||
|
|
||||||
|
|
||||||
def compile_line(string):
|
def compile_line(string):
|
||||||
# newline appended to solve unexpected EOF issues
|
# newline appended to solve unexpected EOF issues
|
||||||
return compile(string + '\n', '<string>', 'single', optimize=2, dont_inherit=True)
|
return compile(string + '\n', '<string>', 'single', optimize=2, dont_inherit=True)
|
||||||
@ -135,8 +139,9 @@ class Plugin:
|
|||||||
ud = h.userdata
|
ud = h.userdata
|
||||||
self.hooks.remove(h)
|
self.hooks.remove(h)
|
||||||
return ud
|
return ud
|
||||||
else:
|
|
||||||
log('Hook not found')
|
log('Hook not found')
|
||||||
|
return None
|
||||||
|
|
||||||
def loadfile(self, filename):
|
def loadfile(self, filename):
|
||||||
try:
|
try:
|
||||||
@ -148,21 +153,22 @@ class Plugin:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.name = self.globals['__module_name__']
|
self.name = self.globals['__module_name__']
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
lib.hexchat_print(lib.ph, b'Failed to load module: __module_name__ must be set')
|
lib.hexchat_print(lib.ph, b'Failed to load module: __module_name__ must be set')
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.version = self.globals.get('__module_version__', '')
|
self.version = self.globals.get('__module_version__', '')
|
||||||
self.description = self.globals.get('__module_description__', '')
|
self.description = self.globals.get('__module_description__', '')
|
||||||
self.ph = lib.hexchat_plugingui_add(lib.ph, filename.encode(),
|
self.ph = lib.hexchat_plugingui_add(lib.ph, filename.encode(), self.name.encode(),
|
||||||
self.name.encode(),
|
self.description.encode(), self.version.encode(), ffi.NULL)
|
||||||
self.description.encode(),
|
|
||||||
self.version.encode(),
|
|
||||||
ffi.NULL)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
lib.hexchat_print(lib.ph, 'Failed to load module: {}'.format(e).encode())
|
lib.hexchat_print(lib.ph, 'Failed to load module: {}'.format(e).encode())
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
@ -171,17 +177,20 @@ class Plugin:
|
|||||||
if hook.is_unload is True:
|
if hook.is_unload is True:
|
||||||
try:
|
try:
|
||||||
hook.callback(hook.userdata)
|
hook.callback(hook.userdata)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log('Failed to run hook:', e)
|
log('Failed to run hook:', e)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
del self.hooks
|
del self.hooks
|
||||||
if self.ph is not None:
|
if self.ph is not None:
|
||||||
lib.hexchat_plugingui_remove(lib.ph, self.ph)
|
lib.hexchat_plugingui_remove(lib.ph, self.ph)
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info[0] is 2:
|
if sys.version_info[0] == 2:
|
||||||
def __decode(string):
|
def __decode(string):
|
||||||
return string
|
return string
|
||||||
|
|
||||||
else:
|
else:
|
||||||
def __decode(string):
|
def __decode(string):
|
||||||
return string.decode()
|
return string.decode()
|
||||||
@ -192,6 +201,7 @@ def wordlist_len(words):
|
|||||||
for i in range(31, 1, -1):
|
for i in range(31, 1, -1):
|
||||||
if ffi.string(words[i]):
|
if ffi.string(words[i]):
|
||||||
return i
|
return i
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@ -205,24 +215,26 @@ def create_wordlist(words):
|
|||||||
# This makes no sense to do...
|
# This makes no sense to do...
|
||||||
def create_wordeollist(words):
|
def create_wordeollist(words):
|
||||||
words = reversed(words)
|
words = reversed(words)
|
||||||
last = None
|
|
||||||
accum = None
|
accum = None
|
||||||
ret = []
|
ret = []
|
||||||
for word in words:
|
for word in words:
|
||||||
if accum is None:
|
if accum is None:
|
||||||
accum = word
|
accum = word
|
||||||
|
|
||||||
elif word:
|
elif word:
|
||||||
last = accum
|
last = accum
|
||||||
accum = ' '.join((word, last))
|
accum = ' '.join((word, last))
|
||||||
|
|
||||||
ret.insert(0, accum)
|
ret.insert(0, accum)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def to_cb_ret(value):
|
def to_cb_ret(value):
|
||||||
if value is None:
|
if value is None:
|
||||||
return 0
|
return 0
|
||||||
else:
|
|
||||||
return int(value)
|
return int(value)
|
||||||
|
|
||||||
|
|
||||||
@ffi.def_extern()
|
@ffi.def_extern()
|
||||||
@ -274,13 +286,14 @@ def _on_timer_hook(userdata):
|
|||||||
hook = ffi.from_handle(userdata)
|
hook = ffi.from_handle(userdata)
|
||||||
if hook.callback(hook.userdata) is True:
|
if hook.callback(hook.userdata) is True:
|
||||||
return 1
|
return 1
|
||||||
else:
|
|
||||||
hook.is_unload = True # Don't unhook
|
hook.is_unload = True # Don't unhook
|
||||||
for h in hook.plugin.hooks:
|
for h in hook.plugin.hooks:
|
||||||
if h == hook:
|
if h == hook:
|
||||||
hook.plugin.hooks.remove(h)
|
hook.plugin.hooks.remove(h)
|
||||||
break
|
break
|
||||||
return 0
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@ffi.def_extern(error=3)
|
@ffi.def_extern(error=3)
|
||||||
@ -291,6 +304,7 @@ def _on_say_command(word, word_eol, userdata):
|
|||||||
lib.hexchat_print(lib.ph, b'>>> ' + python)
|
lib.hexchat_print(lib.ph, b'>>> ' + python)
|
||||||
exec_in_interp(__decode(python))
|
exec_in_interp(__decode(python))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@ -298,33 +312,36 @@ def load_filename(filename):
|
|||||||
filename = os.path.expanduser(filename)
|
filename = os.path.expanduser(filename)
|
||||||
if not os.path.isabs(filename):
|
if not os.path.isabs(filename):
|
||||||
configdir = __decode(ffi.string(lib.hexchat_get_info(lib.ph, b'configdir')))
|
configdir = __decode(ffi.string(lib.hexchat_get_info(lib.ph, b'configdir')))
|
||||||
|
|
||||||
filename = os.path.join(configdir, 'addons', filename)
|
filename = os.path.join(configdir, 'addons', filename)
|
||||||
|
|
||||||
if filename and not any(plugin.filename == filename for plugin in plugins):
|
if filename and not any(plugin.filename == filename for plugin in plugins):
|
||||||
plugin = Plugin()
|
plugin = Plugin()
|
||||||
if plugin.loadfile(filename):
|
if plugin.loadfile(filename):
|
||||||
plugins.add(plugin)
|
plugins.add(plugin)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def unload_name(name):
|
def unload_name(name):
|
||||||
if name:
|
if name:
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
if name in (plugin.name, plugin.filename,
|
if name in (plugin.name, plugin.filename, os.path.basename(plugin.filename)):
|
||||||
os.path.basename(plugin.filename)):
|
|
||||||
plugins.remove(plugin)
|
plugins.remove(plugin)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def reload_name(name):
|
def reload_name(name):
|
||||||
if name:
|
if name:
|
||||||
for plugin in plugins:
|
for plugin in plugins:
|
||||||
if name in (plugin.name, plugin.filename,
|
if name in (plugin.name, plugin.filename, os.path.basename(plugin.filename)):
|
||||||
os.path.basename(plugin.filename)):
|
|
||||||
filename = plugin.filename
|
filename = plugin.filename
|
||||||
plugins.remove(plugin)
|
plugins.remove(plugin)
|
||||||
return load_filename(filename)
|
return load_filename(filename)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@ -346,6 +363,7 @@ def autoload():
|
|||||||
log('Autoloading', f)
|
log('Autoloading', f)
|
||||||
# TODO: Set cwd
|
# TODO: Set cwd
|
||||||
load_filename(os.path.join(addondir, f))
|
load_filename(os.path.join(addondir, f))
|
||||||
|
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
log('Autoload failed', e)
|
log('Autoload failed', e)
|
||||||
|
|
||||||
@ -376,7 +394,6 @@ def list_plugins():
|
|||||||
for row in tbl:
|
for row in tbl:
|
||||||
lib.hexchat_print(lib.ph, b' '.join(item.ljust(column_sizes[i])
|
lib.hexchat_print(lib.ph, b' '.join(item.ljust(column_sizes[i])
|
||||||
for i, item in enumerate(row)))
|
for i, item in enumerate(row)))
|
||||||
|
|
||||||
lib.hexchat_print(lib.ph, b'')
|
lib.hexchat_print(lib.ph, b'')
|
||||||
|
|
||||||
|
|
||||||
@ -396,6 +413,7 @@ def exec_in_interp(python):
|
|||||||
ret = eval(code, local_interp.globals, local_interp.locals)
|
ret = eval(code, local_interp.globals, local_interp.locals)
|
||||||
if ret is not None:
|
if ret is not None:
|
||||||
lib.hexchat_print(lib.ph, '{}'.format(ret).encode())
|
lib.hexchat_print(lib.ph, '{}'.format(ret).encode())
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc(file=hexchat_stdout)
|
traceback.print_exc(file=hexchat_stdout)
|
||||||
|
|
||||||
@ -406,6 +424,7 @@ def _on_load_command(word, word_eol, userdata):
|
|||||||
if filename.endswith(b'.py'):
|
if filename.endswith(b'.py'):
|
||||||
load_filename(__decode(filename))
|
load_filename(__decode(filename))
|
||||||
return 3
|
return 3
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@ -415,6 +434,7 @@ def _on_unload_command(word, word_eol, userdata):
|
|||||||
if filename.endswith(b'.py'):
|
if filename.endswith(b'.py'):
|
||||||
unload_name(__decode(filename))
|
unload_name(__decode(filename))
|
||||||
return 3
|
return 3
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@ -424,6 +444,7 @@ def _on_reload_command(word, word_eol, userdata):
|
|||||||
if filename.endswith(b'.py'):
|
if filename.endswith(b'.py'):
|
||||||
reload_name(__decode(filename))
|
reload_name(__decode(filename))
|
||||||
return 3
|
return 3
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@ -434,23 +455,30 @@ def _on_py_command(word, word_eol, userdata):
|
|||||||
if subcmd == 'exec':
|
if subcmd == 'exec':
|
||||||
python = __decode(ffi.string(word_eol[3]))
|
python = __decode(ffi.string(word_eol[3]))
|
||||||
exec_in_interp(python)
|
exec_in_interp(python)
|
||||||
|
|
||||||
elif subcmd == 'load':
|
elif subcmd == 'load':
|
||||||
filename = __decode(ffi.string(word[3]))
|
filename = __decode(ffi.string(word[3]))
|
||||||
load_filename(filename)
|
load_filename(filename)
|
||||||
|
|
||||||
elif subcmd == 'unload':
|
elif subcmd == 'unload':
|
||||||
name = __decode(ffi.string(word[3]))
|
name = __decode(ffi.string(word[3]))
|
||||||
if not unload_name(name):
|
if not unload_name(name):
|
||||||
lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name')
|
lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name')
|
||||||
|
|
||||||
elif subcmd == 'reload':
|
elif subcmd == 'reload':
|
||||||
name = __decode(ffi.string(word[3]))
|
name = __decode(ffi.string(word[3]))
|
||||||
if not reload_name(name):
|
if not reload_name(name):
|
||||||
lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name')
|
lib.hexchat_print(lib.ph, b'Can\'t find a python plugin with that name')
|
||||||
|
|
||||||
elif subcmd == 'console':
|
elif subcmd == 'console':
|
||||||
lib.hexchat_command(lib.ph, b'QUERY >>python<<')
|
lib.hexchat_command(lib.ph, b'QUERY >>python<<')
|
||||||
|
|
||||||
elif subcmd == 'list':
|
elif subcmd == 'list':
|
||||||
list_plugins()
|
list_plugins()
|
||||||
|
|
||||||
elif subcmd == 'about':
|
elif subcmd == 'about':
|
||||||
lib.hexchat_print(lib.ph, b'HexChat Python interface version ' + VERSION)
|
lib.hexchat_print(lib.ph, b'HexChat Python interface version ' + VERSION)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
lib.hexchat_command(lib.ph, b'HELP PY')
|
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')
|
modpath = os.path.join(libdir, '..', 'python')
|
||||||
sys.path.append(os.path.abspath(modpath))
|
sys.path.append(os.path.abspath(modpath))
|
||||||
hexchat = importlib.import_module('hexchat')
|
hexchat = importlib.import_module('hexchat')
|
||||||
|
|
||||||
except (UnicodeDecodeError, ImportError) as e:
|
except (UnicodeDecodeError, ImportError) as e:
|
||||||
lib.hexchat_print(lib.ph, b'Failed to import module: ' + repr(e).encode())
|
lib.hexchat_print(lib.ph, b'Failed to import module: ' + repr(e).encode())
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
hexchat_stdout = Stdout()
|
hexchat_stdout = Stdout()
|
||||||
@ -517,6 +547,7 @@ def _on_plugin_deinit():
|
|||||||
for mod in ('_hexchat', 'hexchat', 'xchat', '_hexchat_embedded'):
|
for mod in ('_hexchat', 'hexchat', 'xchat', '_hexchat_embedded'):
|
||||||
try:
|
try:
|
||||||
del sys.modules[mod]
|
del sys.modules[mod]
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
26
plugins/python/python_style_guide.md
Normal file
26
plugins/python/python_style_guide.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# HexChat Python Module Style Guide
|
||||||
|
|
||||||
|
(This is a work in progress).
|
||||||
|
|
||||||
|
## General rules
|
||||||
|
|
||||||
|
- PEP8 as general fallback recommendations
|
||||||
|
- Max line length: 120
|
||||||
|
- Avoid overcomplex compound statements. i.e. dont do this: `somevar = x if x == y else z if a == b and c == b else x`
|
||||||
|
|
||||||
|
## Indentation style
|
||||||
|
|
||||||
|
### Multi-line functions
|
||||||
|
|
||||||
|
```python
|
||||||
|
foo(really_long_arg_1,
|
||||||
|
really_long_arg_2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mutli-line lists/dicts
|
||||||
|
|
||||||
|
```python
|
||||||
|
foo = {
|
||||||
|
'bar': 'baz',
|
||||||
|
}
|
||||||
|
```
|
Loading…
Reference in New Issue
Block a user