diff --git a/plugins/alias.py b/plugins/alias.py index 163e4c26..4d4a3bf4 100644 --- a/plugins/alias.py +++ b/plugins/alias.py @@ -6,7 +6,7 @@ Allows the creation and the removal of personal aliases. from plugin import BasePlugin import common -from common import parse_command_args_to_alias as parse +from common import shell_split class Plugin(BasePlugin): def init(self): @@ -27,18 +27,18 @@ class Plugin(BasePlugin): """ arg = common.shell_split(line) if len(arg) < 2: - self.core.information('Alias: Not enough parameters', 'Error') + self.api.information('Alias: Not enough parameters', 'Error') return alias = arg[0] command = arg[1] tmp_args = arg[2] if len(arg) > 2 else '' if alias in self.core.commands or alias in self.commands: - self.core.information('Alias: command already exists', 'Error') + self.api.information('Alias: command already exists', 'Error') return - self.commands[alias] = lambda arg: self.get_command(command)(parse(arg, tmp_args)) + self.commands[alias] = lambda arg: self.get_command(command)(tmp_args.format(*shell_split(arg))) self.add_command(alias, self.commands[alias], 'This command is an alias for /%s %s' %( command, tmp_args)) - self.core.information('Alias /%s successfuly created' % alias, 'Info') + self.api.information('Alias /%s successfuly created' % alias, 'Info') def command_unalias(self, alias): """ @@ -47,7 +47,7 @@ class Plugin(BasePlugin): if alias in self.commands: del self.commands[alias] self.del_command(alias) - self.core.information('Alias /%s successfuly deleted' % alias, 'Info') + self.api.information('Alias /%s successfuly deleted' % alias, 'Info') def completion_unalias(self, the_input): aliases = [alias for alias in self.commands] @@ -61,6 +61,6 @@ class Plugin(BasePlugin): pass if name in self.core.commands: return self.core.commands[name][0] - elif name in self.core.current_tab().commands: - return self.core.current_tab().commands[name][0] + elif name in self.api.current_tab().commands: + return self.api.current_tab().commands[name][0] return dummy diff --git a/src/common.py b/src/common.py index e4e3a52d..1a3027e7 100644 --- a/src/common.py +++ b/src/common.py @@ -6,7 +6,7 @@ # it under the terms of the zlib license. See the COPYING file. """ -various useful functions +Various useful functions. """ from datetime import datetime, timedelta @@ -29,6 +29,13 @@ ROOM_STATE_HL = 13 def get_base64_from_file(path): """ Convert the content of a file to base64 + + :param str path: The path of the file to convert. + :return: A tuple of (encoded data, mime type, sha1 hash) if + the file exists and does not exceeds the upper size limit of 16384. + :return: (None, None, error message) if it fails + :rtype: :py:class:`tuple` + """ if not os.path.isfile(path): return (None, None, "File does not exist") @@ -44,7 +51,11 @@ def get_base64_from_file(path): def get_output_of_command(command): """ - Runs a command and returns its output + Runs a command and returns its output. + + :param str command: The command to run. + :return: The output or None + :rtype: :py:class:`str` """ try: return subprocess.check_output(command.split()).decode('utf-8').split('\n') @@ -53,9 +64,14 @@ def get_output_of_command(command): def is_in_path(command, return_abs_path=False): """ - Return True if 'command' is found in one of the directories in the user's - path. If 'return_abs_path' is True, return the absolute path of the first - found command instead. Return False otherwise and on errors + Check if *command* is in the $PATH or not. + + :param str command: The command to be checked. + :param bool return_abs_path: Return the absolute path of the command instead + of True if the command is found. + :return: True if the command is found, the command path if the command is found + and *return_abs_path* is True, otherwise False. + """ for directory in os.getenv('PATH').split(os.pathsep): try: @@ -95,6 +111,8 @@ def get_os_info(): """ Returns a detailed and well formated string containing informations about the operating system + + :rtype: str """ if os.name == 'posix': executable = 'lsb_release' @@ -151,14 +169,21 @@ def get_os_info(): def datetime_tuple(timestamp): """ - Convert timestamp using strptime and the format: %Y%m%dT%H:%M:%S + Convert a timestamp using strptime and the format: %Y%m%dT%H:%M:%S. - Because of various datetime formats are used the following exceptions + Because various datetime formats are used, the following exceptions are handled: - - Optional milliseconds appened to the string are removed - - Optional Z (that means UTC) appened to the string are removed - - XEP-082 datetime strings have all '-' cahrs removed to meet - the above format. + + * Optional milliseconds appened to the string are removed + * Optional Z (that means UTC) appened to the string are removed + * XEP-082 datetime strings have all '-' chars removed to meet the above format. + + :param str timestamp: The string containing the formatted date. + :return: The date. + :rtype: :py:class:`datetime.datetime` + + >>> common.datetime_tuple('20130226T06:23:12') + datetime.datetime(2013, 2, 26, 8, 23, 12) """ timestamp = timestamp.split('.')[0] timestamp = timestamp.replace('-', '') @@ -172,6 +197,17 @@ def datetime_tuple(timestamp): return ret def shell_split(st): + """ + Split a string correctly according to the quotes + around the elements. + + :param str st: The string to split. + :return: A list of the different of the string. + :rtype: :py:class:`list` + + >>> shell_split('"sdf 1" "toto 2"') + ['sdf 1', 'toto 2'] + """ sh = shlex.shlex(st, posix=True) sh.commenters = '' sh.whitespace_split = True @@ -188,7 +224,12 @@ def shell_split(st): def parse_str_to_secs(duration=''): """ - Parse a string of with a number of d, h, m, s + Parse a string of with a number of d, h, m, s. + + :param str duration: The formatted string. + :return: The number of seconds represented by the string + :rtype: :py:class:`int` + >>> parse_str_to_secs("1d3m1h") 90180 """ @@ -210,8 +251,15 @@ def parse_str_to_secs(duration=''): def parse_secs_to_str(duration=0): """ + Do the reverse operation of :py:func:`parse_str_to_secs`. + Parse a number of seconds to a human-readable string. The string has the form XdXhXmXs. 0 units are removed. + + :param int duration: The duration, in seconds. + :return: A formatted string containing the duration. + :rtype: :py:class:`str` + >>> parse_secs_to_str(3601) 1h1s """ @@ -230,36 +278,13 @@ def parse_secs_to_str(duration=0): result = '0s' return result -def parse_command_args_to_alias(arg, strto): - """ - Parse command parameters. - Numbers can be from 0 to 9. - >>> parse_command_args_to_alias('sdf koin', '%1 %0') - "koin sdf" - """ - if '%' not in strto: - return strto + arg - args = shell_split(arg) - l = len(args) - dest = '' - var_num = False - for i in strto: - if i != '%': - if not var_num: - dest += i - elif i in string.digits: - if 0 <= int(i) < l: - dest += args[int(i)] - var_num = False - elif i == '%': - if var_num: - dest += '%' - var_num = False - else: - var_num = True - return dest - def safeJID(*args, **kwargs): + """ + Construct a :py:class:`sleekxmpp.JID` object from a string. + + Used to avoid tracebacks during is stringprep fails + (fall back to a JID with an empty string). + """ try: return JID(*args, **kwargs) except InvalidJID: diff --git a/src/plugin.py b/src/plugin.py index 602d0326..2ef83510 100644 --- a/src/plugin.py +++ b/src/plugin.py @@ -6,6 +6,7 @@ These are used in the plugin system added in poezio 0.7.5 import os from functools import partial from configparser import RawConfigParser +from timed_events import TimedEvent, DelayedEvent import config import inspect import traceback @@ -120,12 +121,49 @@ class PluginAPI(object): def add_timed_event(self, _, *args, **kwargs): """ - Add a timed event. + Schedule a timed event. - :param event: The timed event to add. + :param timed_events.TimedEvent event: The timed event to schedule. """ return self.core.add_timed_event(*args, **kwargs) + def remove_timed_event(self, _, *args, **kwargs): + """ + Unschedule a timed event. + + :param timed_events.TimedEvent event: The event to unschedule. + """ + return self.core.remove_timed_event(*args, **kwargs) + + def create_timed_event(self, _, *args, **kwargs): + """ + Create a timed event, but do not schedule it; + :py:func:`~PluginAPI.add_timed_event` must be used for that. + + :param datetime.datetime date: The time at which the handler must be executed + :param function callback: The handler that will be executed + :param \*args: Optional arguments passed to the handler. + :return: The created event. + :rtype: :py:class:`timed_events.TimedEvent` + """ + return TimedEvent(*args, **kwargs) + + def create_delayed_event(self, _, *args, **kwargs): + """ + Create a delayed event, but do not schedule it; + :py:func:`~PluginAPI.add_timed_event` must be used for that. + + A delayed event is a timed event with a delay from the time + this function is called (instead of a datetime). + + :param int delay: The number of seconds to schedule the execution + :param function callback: The handler that will be executed + :param \*args: Optional arguments passed to the handler. + :return: The created event. + :rtype: :py:class:`timed_events.DelayedEvent` + """ + return DelayedEvent(*args, **kwargs) + def information(self, _, *args, **kwargs): """ Display a new message in the information buffer. @@ -139,7 +177,7 @@ class PluginAPI(object): """ Get the current Tab. - :returns: tabs.Tab The current tab. + :returns: The current tab. """ return self.core.current_tab() @@ -176,7 +214,7 @@ class PluginAPI(object): Example string: " [port]" - :raises: Exception If the command already exists. + :raises Exception: If the command already exists. """ return self.plugin_manager.add_command(module, *args, **kwargs) diff --git a/src/timed_events.py b/src/timed_events.py index 5be2aba2..a922ee03 100644 --- a/src/timed_events.py +++ b/src/timed_events.py @@ -6,8 +6,11 @@ # it under the terms of the zlib license. See the COPYING file. """ -To use these, just use core.add_timed_event(event) -where event is an instance of one of these classes +Timed events are the standard way to schedule events for later in poezio. + +Once created, they must be added to the list of checked events with +:py:func:`Core.add_timed_event` (within poezio) or with +:py:func:`.PluginAPI.add_timed_event` (within a plugin). """ import logging @@ -18,13 +21,22 @@ import datetime class TimedEvent(object): """ - An event with a callback that is called when the specified time is passed + An event with a callback that is called when the specified time is passed. + Note that these events can NOT be used for very small delay or a very precise date, since the check for events is done once per second, as a maximum. + The callback and its arguments should be passed as the lasts arguments. """ def __init__(self, date, callback, *args): + """ + Create a new timed event. + + :param datetime.datetime date: Time at which the callback must be run. + :param function callback: The handler that will be executed. + :param \*args: Optional arguments passed to the handler. + """ self._callback = callback self.args = args self.repetive = False @@ -42,7 +54,11 @@ class TimedEvent(object): def has_timed_out(self, current_date): """ - returns True if the callback should be called + Check if the event has timed out. + + :param datetime.datetime current_date: The current date. + :returns: True if the callback should be called + :rtype: bool """ if self.next_call_date < current_date: return True @@ -51,22 +67,33 @@ class TimedEvent(object): def change_date(self, date): """ - Simply change the date of the event + Simply change the date of the event. + + :param datetime.datetime date: Next date. """ self.next_call_date = date def add_delay(self, delay): """ - Add a delay (in seconds) to the date + Add a delay (in seconds) to the date. + + :param int delay: The delay to add. """ self.next_call_date += datetime.timedelta(seconds=delay) class DelayedEvent(TimedEvent): """ - The date is calculated from now + a delay in seconds - Use it if you want an event to happen in, e.g. 6 seconds + A TimedEvent, but with the date calculated from now + a delay in seconds. + Use it if you want an event to happen in, e.g. 6 seconds. """ def __init__(self, delay, callback, *args): + """ + Create a new DelayedEvent. + + :param int delay: The number of seconds. + :param function callback: The handler that will be executed. + :param \*args: Optional arguments passed to the handler. + """ date = datetime.datetime.now() + datetime.timedelta(seconds=delay) TimedEvent.__init__(self, date, callback, *args)