# -*- coding: utf-8 -*- ### # Copyright (c) 2010 by Elián Hanisch # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ### ### # Python interpreter for WeeChat, and module for debugging python scripts. # # Commands (see detailed help with /help in WeeChat): # * /pybuffer: Opens a python interpreter buffer. # # For debug a script, insert these lines after script's register() call. # # import pybuffer # debug = pybuffer.debugBuffer(globals(), "buffer_name") # # Then, after loading your script, try "dir()" in the new "buffer_name" buffer, it should # display the script's global functions and variables. # This module should be in your python scripts path. # # Session example: # # >>> buffer_search('irc', 'freenode.#weechat') # '0x9ca4ce0' # >>> b = buffer_search('irc', 'freenode.#weechat') # >>> b # '0x9ca4ce0' # >>> buffer_get(b, 'input') # Traceback (most recent call last): # File "", line 1, in # NameError: name 'buffer_get' is not defined # >>> search('buffer') # ['buffer_clear', 'buffer_close', 'buffer_get_integer', 'buffer_get_pointer', 'buffer_get_string', # 'buffer_merge', 'buffer_new', 'buffer_search', 'buffer_search_main', 'buffer_set', # 'buffer_string_replace_local_var', 'buffer_unmerge', 'current_buffer', 'string_input_for_buffer'] # >>> buffer_get_string(b, 'input') # '' # >>> buffer_get_string(b, 'input') # 'asdasdas hello!' # >>> buffer_get_string(b, 'title') # 'WeeChat, stable: 0.3.3, web: http://www.weechat.org/ | English support channel | Please read # doc/faq/quickstart before asking here | Old versions (0.2.6.x and earlier) are not supported any # more' # # # History: # 2018-10-03 # version 0.3: # * Python3 compatibility (Pol Van Aubel ) # # 2010-11-05 # version 0.2: # * More interperter console. # * renamed to pybuffer. # * added /pybuffer command (buffer isn't created on script load). # # 2010-10-30 # version 0.1: Initial release ### from __future__ import print_function import sys # Only required for python version check. PY2 = sys.version_info < (3,) SCRIPT_NAME = "pybuffer" SCRIPT_AUTHOR = "Elián Hanisch " SCRIPT_VERSION = "0.3" SCRIPT_LICENSE = "GPL3" SCRIPT_DESC = "Python interpreter for WeeChat and module for debug scripts." try: import weechat from weechat import WEECHAT_RC_OK, prnt import_ok = True except ImportError: print("This script must be run under WeeChat.") print("Get WeeChat now at: https://weechat.org/") import_ok = False import code, sys, traceback from fnmatch import fnmatch def callback(method): """This function will take a bound method or function and make it a callback.""" # try to create a descriptive and unique name. funcname = method.__name__ try: classinst = method.__self__ try: instname = classinst.__name__ except AttributeError: try: instname = classinst.name except AttributeError: raise Exception("Instance %s has no __name__ attribute" % classinst) classname = type(classinst).__name__ name = '_'.join((classname, instname, funcname)) except AttributeError: # not a bound method name = funcname # set our callback import __main__ setattr(__main__, name, method) return name class NoArguments(Exception): pass class ArgumentError(Exception): pass class Command(object): """Class for hook WeeChat commands.""" description, usage, help = "WeeChat command.", "[define usage template]", "detailed help here" command = '' completion = '' def __init__(self): assert self.command, "No command defined" self.__name__ = self.command self._pointer = '' self._callback = '' def __call__(self, *args): return self.callback(*args) def callback(self, data, buffer, args): """Called by WeeChat when /command is used.""" self.data, self.buffer, self.args = data, buffer, args try: self.parser(args) # argument parsing except ArgumentError as e: error('Argument error, %s' %e) except NoArguments: pass else: self.execute() return WEECHAT_RC_OK def parser(self, args): """Argument parsing, override if needed.""" pass def execute(self): """This method is called when the command is run, override this.""" pass def hook(self): assert not self._pointer, \ "There's already a hook pointer, unhook first (%s)" %self.command self._callback = callback(self.callback) pointer = weechat.hook_command(self.command, self.description, self.usage, self.help, self.completion, self._callback, '') if pointer == '': raise Exception("hook_command failed: %s %s" %(SCRIPT_NAME, self.command)) self._pointer = pointer def unhook(self): if self._pointer: weechat.unhook(self._pointer) self._pointer = '' self._callback = '' class SimpleBuffer(object): """WeeChat buffer. Only for displaying lines.""" _title = '' def __init__(self, name): assert name, "Buffer needs a name." self.__name__ = name self._pointer = '' def _getBuffer(self): # we need to always search the buffer, since there's no close callback we can't know if the # buffer was closed. buffer = weechat.buffer_search('python', self.__name__) if not buffer: buffer = self.create() return buffer def _create(self): return weechat.buffer_new(self.__name__, '', '', '', '') def create(self): buffer = self._create() if self._title: weechat.buffer_set(buffer, 'title', self._title) self._pointer = buffer return buffer def title(self, s): self._title = s weechat.buffer_set(self._getBuffer(), 'title', s) def clear(self): weechat.buffer_clear(self._getBuffer()) def __call__(self, s, *args, **kwargs): self.prnt(s, *args, **kwargs) def display(self): weechat.buffer_set(self._getBuffer(), 'display', '1') def error(self, s, *args): self.prnt(s, prefix=weechat.prefix('error')) def prnt(self, s, *args, **kwargs): """Prints messages in buffer.""" buffer = self._getBuffer() # Ensure s is a str (Py3) or a str/unicode (Py2) so that s % args will work. if PY2: # Don't accidentally convert unicode to utf-8 or ASCII or whatever because we want to see the object. if not isinstance(s, basestring): s = str(s) else: if not isinstance(s, str): s = str(s) if args: s = s % args try: s = kwargs['prefix'] + s except KeyError: pass prnt(buffer, s) def prnt_lines(self, s, *args, **kwargs): for line in s.splitlines(): self.prnt(line, *args, **kwargs) class Buffer(SimpleBuffer): """WeeChat buffer. With input and close methods.""" def _create(self): return weechat.buffer_new(self.__name__, callback(self.input), '', callback(self.close), '') def _getBuffer(self): if self._pointer: return self._pointer return SimpleBuffer._getBuffer(self) def input(self, data, buffer, input): return WEECHAT_RC_OK def close(self, data, buffer): self._pointer = '' return WEECHAT_RC_OK class StreamObject(object): def __init__(self, buffer): self._content = '' self._buffer = buffer def write(self, s): self._content += s def prnt(self, *args, **kwargs): if self._content: self._buffer.prnt_lines(self._content, *args, **kwargs) self._content = '' class PythonBuffer(Buffer): _title = "Python Buffer: use search([pattern]) for a list of objects." def __init__(self, name, locals=None): Buffer.__init__(self, name) self.output = StreamObject(self) self.error = StreamObject(self) # redirect stdout and stderr sys.stdout = self.output sys.stderr = self.error self.console = code.InteractiveConsole(locals) locals = self.console.locals # add our 'buildin' functions if 'search' not in locals: def search(s=''): """List functions/objects that match 's' in their name.""" if '*' not in s: s = '*%s*' %s return [ name for name in locals if fnmatch(name, s) ] locals['search'] = search def _create(self): buffer = Buffer._create(self) weechat.buffer_set(buffer, 'nicklist', '0') weechat.buffer_set(buffer, 'time_for_each_line', '0') weechat.buffer_set(buffer, 'localvar_set_no_log', '1') self.color_input = weechat.color('green') self.color_exc = weechat.color('red') self.color_call = weechat.color('cyan') weechat.hook_command_run('/input return', callback(self.input_return), buffer) # print python and WeeChat version prnt(buffer, "Python %s" % sys.version.split(None, 1)[0]) prnt(buffer, "WeeChat %s" % weechat.info_get('version', '')) return buffer def __call__(self, s, *args, **kwargs): kwargs['prefix'] = self.color_call self.prnt(s, *args, **kwargs) def input_return(self, data, buffer, command): # we need to send returns even when there's no input. if data == buffer and not weechat.buffer_get_string(buffer, 'input'): self.input(data, buffer, '\n') return WEECHAT_RC_OK def input(self, data, buffer, input): """Python code evaluation.""" try: need_more = self.console.push(input) if need_more: prompt = '%s... ' % self.color_input else: prompt = '%s>>> ' % self.color_input self.prnt(input, prefix=prompt) self.output.prnt() self.error.prnt(prefix=self.color_exc) except: trace = traceback.format_exc() self.prnt_lines(trace, prefix=self.color_exc) return WEECHAT_RC_OK def debugBuffer(globals, name='debugBuffer'): buffer = PythonBuffer(name, globals) buffer.create() return buffer class PyBufferCommand(Command): command = SCRIPT_NAME description = usage = help = '' def execute(self): buffer = PythonBuffer(SCRIPT_NAME) buffer.title("Use \"search([pattern])\" for search WeeChat API functions.") # import weechat and its functions. buffer.input('', '', 'import weechat') buffer.input('', '', 'from weechat import *') buffer.display() if __name__ == '__main__' and import_ok and \ weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', ''): # we're being loaded as a script. help = \ "Opens a session with a python interpreter-like buffer."\ " Use function 'search([pattern])' for list objects in current session.\n"\ "\n"\ "For debug a script add these lines after the register call:\n"\ "\n"\ " import pybuffer\n"\ " debug = pybuffer.debugBuffer(globals(), 'name')\n"\ " debug('debug message example')\n"\ "\n"\ "You'll be able to execute python code while your script runs.\n"\ "\n%(b)sWARNING: %(r)s" \ "This script isn't fool-proof, you're very capable of crashing/freezing "\ "WeeChat if you aren't careful with the code you run, use at your own risk."\ " Running python interactive functions such as 'help()' or 'license()' %(b)swill%(r)s hang"\ " WeeChat." %dict(b=weechat.color('bold'), r=weechat.color('reset')) PyBufferCommand.help = help PyBufferCommand().hook() # vim:set shiftwidth=4 tabstop=4 softtabstop=4 expandtab textwidth=100: