diff --git a/bot.py b/bot.py index ca177a9..1b74fca 100755 --- a/bot.py +++ b/bot.py @@ -311,7 +311,7 @@ class Sopel(irc.Bot): # Now that we've sent the first part, we need to send the rest. Doing # this recursively seems easier to me than iteratively if excess: - self.say(recipient, excess, max_messages - 1) + self.say(excess, recipient, max_messages - 1) def notice(self, text, dest): """Send an IRC NOTICE to a user or a channel. diff --git a/config/__init__.py b/config/__init__.py index f97d867..4308c8e 100755 --- a/config/__init__.py +++ b/config/__init__.py @@ -1,4 +1,5 @@ -# coding=utf-8 +#! /usr/bin/env python3 +# -*- coding: utf-8 -*- """ The config object provides a simplified to access Sopel's configuration file. The sections of the file are attributes of the object, and the keys in the @@ -13,9 +14,6 @@ object is initialized. .. versionadded:: 6.0.0 """ -# Copyright 2012-2015, Elsie Powell, embolalia.com -# Copyright © 2012, Elad Alfassa -# Licensed under the Eiffel Forum License 2. from __future__ import unicode_literals, absolute_import, print_function, division @@ -25,239 +23,235 @@ from tools import get_input import loader import os import sys -if sys.version_info.major < 3: - import ConfigParser -else: - basestring = str - import configparser as ConfigParser +import configparser as ConfigParser import config.core_section from config.types import StaticSection class ConfigurationError(Exception): - """ Exception type for configuration errors """ + """ Exception type for configuration errors """ - def __init__(self, value): - self.value = value + def __init__(self, value): + self.value = value - def __str__(self): - return 'ConfigurationError: %s' % self.value + def __str__(self): + return 'ConfigurationError: %s' % self.value class Config(object): - def __init__(self, filename, validate=True): - """The bot's configuration. + def __init__(self, filename, validate=True): + """The bot's configuration. - The given filename will be associated with the configuration, and is - the file which will be written if write() is called. If load is not - given or True, the configuration object will load the attributes from - the file at filename. + The given filename will be associated with the configuration, and is + the file which will be written if write() is called. If load is not + given or True, the configuration object will load the attributes from + the file at filename. - A few default values will be set here if they are not defined in the - config file, or a config file is not loaded. They are documented below. - """ - self.filename = filename - """The config object's associated file, as noted above.""" - self.parser = ConfigParser.RawConfigParser(allow_no_value=True) - self.parser.read(self.filename) - self.define_section('core', config.core_section.CoreSection, - validate=validate) - self.get = self.parser.get + A few default values will be set here if they are not defined in the + config file, or a config file is not loaded. They are documented below. + """ + self.filename = filename + """The config object's associated file, as noted above.""" + self.parser = ConfigParser.RawConfigParser(allow_no_value=True) + self.parser.read(self.filename) + self.define_section('core', config.core_section.CoreSection, + validate=validate) + self.get = self.parser.get - @property - def homedir(self): - """An alias to config.core.homedir""" - # Technically it's the other way around, so we can bootstrap filename - # attributes in the core section, but whatever. - configured = None - if self.parser.has_option('core', 'homedir'): - configured = self.parser.get('core', 'homedir') - if configured: - return configured - else: - return os.path.dirname(self.filename) + @property + def homedir(self): + """An alias to config.core.homedir""" + # Technically it's the other way around, so we can bootstrap filename + # attributes in the core section, but whatever. + configured = None + if self.parser.has_option('core', 'homedir'): + configured = self.parser.get('core', 'homedir') + if configured: + return configured + else: + return os.path.dirname(self.filename) - def save(self): - """Save all changes to the config file.""" - cfgfile = open(self.filename, 'w') - self.parser.write(cfgfile) - cfgfile.flush() - cfgfile.close() + def save(self): + """Save all changes to the config file.""" + cfgfile = open(self.filename, 'w') + self.parser.write(cfgfile) + cfgfile.flush() + cfgfile.close() - def add_section(self, name): - """Add a section to the config file. + def add_section(self, name): + """Add a section to the config file. - Returns ``False`` if already exists. - """ - try: - return self.parser.add_section(name) - except ConfigParser.DuplicateSectionError: - return False + Returns ``False`` if already exists. + """ + try: + return self.parser.add_section(name) + except ConfigParser.DuplicateSectionError: + return False - def define_section(self, name, cls_, validate=True): - """Define the available settings in a section. + def define_section(self, name, cls_, validate=True): + """Define the available settings in a section. - ``cls_`` must be a subclass of ``StaticSection``. If the section has - already been defined with a different class, ValueError is raised. + ``cls_`` must be a subclass of ``StaticSection``. If the section has + already been defined with a different class, ValueError is raised. - If ``validate`` is True, the section's values will be validated, and an - exception raised if they are invalid. This is desirable in a module's - setup function, for example, but might not be in the configure function. - """ - if not issubclass(cls_, StaticSection): - raise ValueError("Class must be a subclass of StaticSection.") - current = getattr(self, name, None) - current_name = str(current.__class__) - new_name = str(cls_) - if (current is not None and not isinstance(current, self.ConfigSection) - and not current_name == new_name): - raise ValueError( - "Can not re-define class for section from {} to {}.".format( - current_name, new_name) - ) - setattr(self, name, cls_(self, name, validate=validate)) + If ``validate`` is True, the section's values will be validated, and an + exception raised if they are invalid. This is desirable in a module's + setup function, for example, but might not be in the configure function. + """ + if not issubclass(cls_, StaticSection): + raise ValueError("Class must be a subclass of StaticSection.") + current = getattr(self, name, None) + current_name = str(current.__class__) + new_name = str(cls_) + if (current is not None and not isinstance(current, self.ConfigSection) + and not current_name == new_name): + raise ValueError( + "Can not re-define class for section from {} to {}.".format( + current_name, new_name) + ) + setattr(self, name, cls_(self, name, validate=validate)) - class ConfigSection(object): + class ConfigSection(object): - """Represents a section of the config file. + """Represents a section of the config file. - Contains all keys in thesection as attributes. + Contains all keys in thesection as attributes. - """ + """ - def __init__(self, name, items, parent): - object.__setattr__(self, '_name', name) - object.__setattr__(self, '_parent', parent) - for item in items: - value = item[1].strip() - if not value.lower() == 'none': - if value.lower() == 'false': - value = False - object.__setattr__(self, item[0], value) + def __init__(self, name, items, parent): + object.__setattr__(self, '_name', name) + object.__setattr__(self, '_parent', parent) + for item in items: + value = item[1].strip() + if not value.lower() == 'none': + if value.lower() == 'false': + value = False + object.__setattr__(self, item[0], value) - def __getattr__(self, name): - return None + def __getattr__(self, name): + return None - def __setattr__(self, name, value): - object.__setattr__(self, name, value) - if type(value) is list: - value = ','.join(value) - self._parent.parser.set(self._name, name, value) + def __setattr__(self, name, value): + object.__setattr__(self, name, value) + if type(value) is list: + value = ','.join(value) + self._parent.parser.set(self._name, name, value) - def get_list(self, name): - value = getattr(self, name) - if not value: - return [] - if isinstance(value, basestring): - value = value.split(',') - # Keep the split value, so we don't have to keep doing this - setattr(self, name, value) - return value + def get_list(self, name): + value = getattr(self, name) + if not value: + return [] + if isinstance(value, str): + value = value.split(',') + # Keep the split value, so we don't have to keep doing this + setattr(self, name, value) + return value - def __getattr__(self, name): - if name in self.parser.sections(): - items = self.parser.items(name) - section = self.ConfigSection(name, items, self) # Return a section - setattr(self, name, section) - return section - else: - raise AttributeError("%r object has no attribute %r" - % (type(self).__name__, name)) + def __getattr__(self, name): + if name in self.parser.sections(): + items = self.parser.items(name) + section = self.ConfigSection(name, items, self) # Return a section + setattr(self, name, section) + return section + else: + raise AttributeError("%r object has no attribute %r" + % (type(self).__name__, name)) - def option(self, question, default=False): - """Ask "y/n" and return the corresponding boolean answer. + def option(self, question, default=False): + """Ask "y/n" and return the corresponding boolean answer. - Show user in terminal a "y/n" prompt, and return true or false based on - the response. If default is passed as true, the default will be shown - as ``[y]``, else it will be ``[n]``. ``question`` should be phrased as - a question, but without a question mark at the end. + Show user in terminal a "y/n" prompt, and return true or false based on + the response. If default is passed as true, the default will be shown + as ``[y]``, else it will be ``[n]``. ``question`` should be phrased as + a question, but without a question mark at the end. - """ - d = 'n' - if default: - d = 'y' - ans = get_input(question + ' (y/n)? [' + d + '] ') - if not ans: - ans = d - return ans.lower() == 'y' + """ + d = 'n' + if default: + d = 'y' + ans = get_input(question + ' (y/n)? [' + d + '] ') + if not ans: + ans = d + return ans.lower() == 'y' - def _modules(self): - home = os.getcwd() - modules_dir = os.path.join(home, 'modules') - filenames = sopel.loader.enumerate_modules(self) - os.sys.path.insert(0, modules_dir) - for name, mod_spec in iteritems(filenames): - path, type_ = mod_spec - try: - module, _ = sopel.loader.load_module(name, path, type_) - except Exception as e: - filename, lineno = sopel.tools.get_raising_file_and_line() - rel_path = os.path.relpath(filename, os.path.dirname(__file__)) - raising_stmt = "%s:%d" % (rel_path, lineno) - stderr("Error loading %s: %s (%s)" % (name, e, raising_stmt)) - else: - if hasattr(module, 'configure'): - prompt = name + ' module' - if module.__doc__: - doc = module.__doc__.split('\n', 1)[0] - if doc: - prompt = doc - prompt = 'Configure {} (y/n)? [n]'.format(prompt) - do_configure = get_input(prompt) - do_configure = do_configure and do_configure.lower() == 'y' - if do_configure: - module.configure(self) - self.save() + def _modules(self): + home = os.getcwd() + modules_dir = os.path.join(home, 'modules') + filenames = sopel.loader.enumerate_modules(self) + os.sys.path.insert(0, modules_dir) + for name, mod_spec in iteritems(filenames): + path, type_ = mod_spec + try: + module, _ = sopel.loader.load_module(name, path, type_) + except Exception as e: + filename, lineno = sopel.tools.get_raising_file_and_line() + rel_path = os.path.relpath(filename, os.path.dirname(__file__)) + raising_stmt = "%s:%d" % (rel_path, lineno) + stderr("Error loading %s: %s (%s)" % (name, e, raising_stmt)) + else: + if hasattr(module, 'configure'): + prompt = name + ' module' + if module.__doc__: + doc = module.__doc__.split('\n', 1)[0] + if doc: + prompt = doc + prompt = 'Configure {} (y/n)? [n]'.format(prompt) + do_configure = get_input(prompt) + do_configure = do_configure and do_configure.lower() == 'y' + if do_configure: + module.configure(self) + self.save() def _wizard(section, config=None): - dotdir = os.path.expanduser('~/.sopel') - configpath = os.path.join(dotdir, (config or 'default') + '.cfg') - if section == 'all': - _create_config(configpath) - elif section == 'mod': - _check_dir(False) - if not os.path.isfile(configpath): - print("No config file found." + - " Please make one before configuring these options.") - sys.exit(1) - config = Config(configpath, validate=False) - config._modules() + dotdir = os.path.expanduser('~/.sopel') + configpath = os.path.join(dotdir, (config or 'default') + '.cfg') + if section == 'all': + _create_config(configpath) + elif section == 'mod': + _check_dir(False) + if not os.path.isfile(configpath): + print("No config file found." + + " Please make one before configuring these options.") + sys.exit(1) + config = Config(configpath, validate=False) + config._modules() def _check_dir(create=True): - dotdir = os.path.join(os.path.expanduser('~'), '.sopel') - if not os.path.isdir(dotdir): - if create: - print('Creating a config directory at ~/.sopel...') - try: - os.makedirs(dotdir) - except Exception as e: - print('There was a problem creating %s:' % dotdir, file=sys.stderr) - print('%s, %s' % (e.__class__, str(e)), file=sys.stderr) - print('Please fix this and then run Sopel again.', file=sys.stderr) - sys.exit(1) - else: - print("No config file found. Please make one before configuring these options.") - sys.exit(1) + dotdir = os.path.join(os.path.expanduser('~'), '.sopel') + if not os.path.isdir(dotdir): + if create: + print('Creating a config directory at ~/.sopel...') + try: + os.makedirs(dotdir) + except Exception as e: + print('There was a problem creating %s:' % dotdir, file=sys.stderr) + print('%s, %s' % (e.__class__, str(e)), file=sys.stderr) + print('Please fix this and then run Sopel again.', file=sys.stderr) + sys.exit(1) + else: + print("No config file found. Please make one before configuring these options.") + sys.exit(1) def _create_config(configpath): - _check_dir() - print("Please answer the following questions" + - " to create your configuration file:\n") - try: - config = Config(configpath, validate=False) - sopel.config.core_section.configure(config) - if config.option( - 'Would you like to see if there are any modules' - ' that need configuring' - ): - config._modules() - config.save() - except Exception: - print("Encountered an error while writing the config file." + - " This shouldn't happen. Check permissions.") - raise - sys.exit(1) - print("Config file written sucessfully!") + _check_dir() + print("Please answer the following questions" + + " to create your configuration file:\n") + try: + config = Config(configpath, validate=False) + sopel.config.core_section.configure(config) + if config.option( + 'Would you like to see if there are any modules' + ' that need configuring' + ): + config._modules() + config.save() + except Exception: + print("Encountered an error while writing the config file." + + " This shouldn't happen. Check permissions.") + raise + sys.exit(1) + print("Config file written sucessfully!") diff --git a/irc.py b/irc.py index e589fc5..910782b 100755 --- a/irc.py +++ b/irc.py @@ -332,12 +332,12 @@ class Bot(asynchat.async_chat): LOGGER.error("Could not save traceback from %s to file: %s", trigger.sender, str(e)) if trigger and self.config.core.reply_errors and trigger.sender is not None: - self.msg(trigger.sender, signature) + self.say(signature, trigger.sender) if trigger: LOGGER.error('Exception from {}: {} ({})'.format(trigger.sender, str(signature), trigger.raw)) except Exception as e: if trigger and self.config.core.reply_errors and trigger.sender is not None: - self.msg(trigger.sender, "Got an error.") + self.say("Got an error.", trigger.sender) if trigger: LOGGER.error('Exception from {}: {} ({})'.format(trigger.sender, str(e), trigger.raw)) diff --git a/loader.py b/loader.py index f81df46..5e00ae8 100755 --- a/loader.py +++ b/loader.py @@ -1,5 +1,4 @@ -#! /usr/bin/env python3 -# -*- coding: utf-8 -*- +#!/usr/bin/env python3 """ Methods for loading modules. """ diff --git a/module.py b/module.py index 14fa298..d39db0b 100755 --- a/module.py +++ b/module.py @@ -1,4 +1,3 @@ -# coding=utf-8 """This contains decorators and tools for creating callable plugin functions. """ # Copyright 2013, Ari Koivula, diff --git a/modules/ascii.py b/modules/ascii.py index 8fafd88..8f357f1 100755 --- a/modules/ascii.py +++ b/modules/ascii.py @@ -1,5 +1,4 @@ -#! /usr/bin/env python3 -# -*- coding: utf-8 -*- +#!/usr/bin/env python3 """ ASCII """ diff --git a/modules/help.py b/modules/help.py index e8fd244..1e0f338 100755 --- a/modules/help.py +++ b/modules/help.py @@ -1,5 +1,4 @@ -#! /usr/bin/env python3 -# -*- coding: utf-8 -*- +#!/usr/bin/env python3 """ help.py - Sopel Help Module Copyright 2008, Sean B. Palmer, inamidst.com diff --git a/modules/remind.py b/modules/remind.py index 2b53f7d..1fbc1e0 100755 --- a/modules/remind.py +++ b/modules/remind.py @@ -70,9 +70,9 @@ def setup(bot): for oldtime in oldtimes: for (channel, nick, message) in bot.rdb[oldtime]: if message: - bot.say(nick + ': ' + message) + bot.say(nick + ': ' + message, channel) else: - bot.say(nick + '!') + bot.say(nick + '!', channel) del bot.rdb[oldtime] dump_database(bot.rfn, bot.rdb) time.sleep(2.5) diff --git a/modules/rundown.py b/modules/rundown.py new file mode 100755 index 0000000..a1e44dc --- /dev/null +++ b/modules/rundown.py @@ -0,0 +1,23 @@ +#! /usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Redpill on the Bogdanovs. +""" +import os +import random + +from module import commands, example + +@commands("rundown") +@example(".rundown") +def grog(bot, trigger): + """ + Provides rundown on demand. + """ + if trigger.group(2) in ["-c", "--cabal"]: + with open(os.path.join(bot.config.homedir, "cabaldown.txt"), "r") as file: + data = file.read() + else: + with open(os.path.join(bot.config.homedir, "rundown.txt"), "r") as file: + data = file.read() + bot.say(data) diff --git a/tools/__init__.py b/tools/__init__.py index f40352f..b93e8ad 100755 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -1,19 +1,10 @@ -# coding=utf-8 -"""Useful miscellaneous tools and shortcuts for Sopel modules +#! /usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Useful miscellaneous tools and shortcuts for Sopel modules *Availability: 3+* """ - -# tools.py - Sopel misc tools -# Copyright 2008, Sean B. Palmer, inamidst.com -# Copyright © 2012, Elad Alfassa -# Copyright 2012, Elsie Powell, embolalia.com -# Licensed under the Eiffel Forum License 2. - -# https://sopel.chat - -from __future__ import unicode_literals, absolute_import, print_function, division - import sys import os import re @@ -24,82 +15,72 @@ from collections import defaultdict from tools._events import events # NOQA -if sys.version_info.major >= 3: - raw_input = input - unicode = str - iteritems = dict.items - itervalues = dict.values - iterkeys = dict.keys -else: - iteritems = dict.iteritems - itervalues = dict.itervalues - iterkeys = dict.iterkeys +iteritems = dict.items +itervalues = dict.values +iterkeys = dict.keys _channel_prefixes = ('#', '&', '+', '!') def get_input(prompt): - """Get decoded input from the terminal (equivalent to python 3's ``input``). - """ - if sys.version_info.major >= 3: - return input(prompt) - else: - return raw_input(prompt).decode('utf8') + """Get decoded input from the terminal (equivalent to python 3's ``input``). + """ + return input(prompt) def get_raising_file_and_line(tb=None): - """Return the file and line number of the statement that raised the tb. + """Return the file and line number of the statement that raised the tb. - Returns: (filename, lineno) tuple + Returns: (filename, lineno) tuple - """ - if not tb: - tb = sys.exc_info()[2] + """ + if not tb: + tb = sys.exc_info()[2] - filename, lineno, _context, _line = traceback.extract_tb(tb)[-1] + filename, lineno, _context, _line = traceback.extract_tb(tb)[-1] - return filename, lineno + return filename, lineno def get_command_regexp(prefix, command): - """Return a compiled regexp object that implements the command.""" - # Escape all whitespace with a single backslash. This ensures that regexp - # in the prefix is treated as it was before the actual regexp was changed - # to use the verbose syntax. - prefix = re.sub(r"(\s)", r"\\\1", prefix) + """Return a compiled regexp object that implements the command.""" + # Escape all whitespace with a single backslash. This ensures that regexp + # in the prefix is treated as it was before the actual regexp was changed + # to use the verbose syntax. + prefix = re.sub(r"(\s)", r"\\\1", prefix) - # This regexp match equivalently and produce the same - # groups 1 and 2 as the old regexp: r'^%s(%s)(?: +(.*))?$' - # The only differences should be handling all whitespace - # like spaces and the addition of groups 3-6. - pattern = r""" - (?:{prefix})({command}) # Command as group 1. - (?:\s+ # Whitespace to end command. - ( # Rest of the line as group 2. - (?:(\S+))? # Parameters 1-4 as groups 3-6. - (?:\s+(\S+))? - (?:\s+(\S+))? - (?:\s+(\S+))? - .* # Accept anything after the parameters. - # Leave it up to the module to parse - # the line. - ))? # Group 2 must be None, if there are no - # parameters. - $ # EoL, so there are no partial matches. - """.format(prefix=prefix, command=command) - return re.compile(pattern, re.IGNORECASE | re.VERBOSE) + # This regexp match equivalently and produce the same + # groups 1 and 2 as the old regexp: r'^%s(%s)(?: +(.*))?$' + # The only differences should be handling all whitespace + # like spaces and the addition of groups 3-6. + pattern = r""" + (?:{prefix})({command}) # Command as group 1. + (?:\s+ # Whitespace to end command. + ( # Rest of the line as group 2. + (?:(\S+))? # Parameters 1-4 as groups 3-6. + (?:\s+(\S+))? + (?:\s+(\S+))? + (?:\s+(\S+))? + .* # Accept anything after the parameters. + # Leave it up to the module to parse + # the line. + ))? # Group 2 must be None, if there are no + # parameters. + $ # EoL, so there are no partial matches. + """.format(prefix=prefix, command=command) + return re.compile(pattern, re.IGNORECASE | re.VERBOSE) def deprecated(old): - def new(*args, **kwargs): - print('Function %s is deprecated.' % old.__name__, file=sys.stderr) - trace = traceback.extract_stack() - for line in traceback.format_list(trace[:-1]): - stderr(line[:-1]) - return old(*args, **kwargs) - new.__doc__ = old.__doc__ - new.__name__ = old.__name__ - return new + def new(*args, **kwargs): + print('Function %s is deprecated.' % old.__name__, file=sys.stderr) + trace = traceback.extract_stack() + for line in traceback.format_list(trace[:-1]): + stderr(line[:-1]) + return old(*args, **kwargs) + new.__doc__ = old.__doc__ + new.__name__ = old.__name__ + return new # from @@ -107,144 +88,144 @@ def deprecated(old): # A simple class to make mutli dimensional dict easy to use class Ddict(dict): - """Class for multi-dimensional ``dict``. + """Class for multi-dimensional ``dict``. - A simple helper class to ease the creation of multi-dimensional ``dict``\s. + A simple helper class to ease the creation of multi-dimensional ``dict``\s. - """ + """ - def __init__(self, default=None): - self.default = default + def __init__(self, default=None): + self.default = default - def __getitem__(self, key): - if key not in self: - self[key] = self.default() - return dict.__getitem__(self, key) + def __getitem__(self, key): + if key not in self: + self[key] = self.default() + return dict.__getitem__(self, key) -class Identifier(unicode): - """A `unicode` subclass which acts appropriately for IRC identifiers. +class Identifier(str): + """A `str` subclass which acts appropriately for IRC identifiers. - When used as normal `unicode` objects, case will be preserved. - However, when comparing two Identifier objects, or comparing a Identifier - object with a `unicode` object, the comparison will be case insensitive. - This case insensitivity includes the case convention conventions regarding - ``[]``, ``{}``, ``|``, ``\\``, ``^`` and ``~`` described in RFC 2812. - """ + When used as normal `str` objects, case will be preserved. + However, when comparing two Identifier objects, or comparing a Identifier + object with a `str` object, the comparison will be case insensitive. + This case insensitivity includes the case convention conventions regarding + ``[]``, ``{}``, ``|``, ``\\``, ``^`` and ``~`` described in RFC 2812. + """ - def __new__(cls, identifier): - # According to RFC2812, identifiers have to be in the ASCII range. - # However, I think it's best to let the IRCd determine that, and we'll - # just assume unicode. It won't hurt anything, and is more internally - # consistent. And who knows, maybe there's another use case for this - # weird case convention. - s = unicode.__new__(cls, identifier) - s._lowered = Identifier._lower(identifier) - return s + def __new__(cls, identifier): + # According to RFC2812, identifiers have to be in the ASCII range. + # However, I think it's best to let the IRCd determine that, and we'll + # just assume str. It won't hurt anything, and is more internally + # consistent. And who knows, maybe there's another use case for this + # weird case convention. + s = str.__new__(cls, identifier) + s._lowered = Identifier._lower(identifier) + return s - def lower(self): - """Return the identifier converted to lower-case per RFC 2812.""" - return self._lowered + def lower(self): + """Return the identifier converted to lower-case per RFC 2812.""" + return self._lowered - @staticmethod - def _lower(identifier): - """Returns `identifier` in lower case per RFC 2812.""" - # The tilde replacement isn't needed for identifiers, but is for - # channels, which may be useful at some point in the future. - low = identifier.lower().replace('{', '[').replace('}', ']') - low = low.replace('|', '\\').replace('^', '~') - return low + @staticmethod + def _lower(identifier): + """Returns `identifier` in lower case per RFC 2812.""" + # The tilde replacement isn't needed for identifiers, but is for + # channels, which may be useful at some point in the future. + low = identifier.lower().replace('{', '[').replace('}', ']') + low = low.replace('|', '\\').replace('^', '~') + return low - def __repr__(self): - return "%s(%r)" % ( - self.__class__.__name__, - self.__str__() - ) + def __repr__(self): + return "%s(%r)" % ( + self.__class__.__name__, + self.__str__() + ) - def __hash__(self): - return self._lowered.__hash__() + def __hash__(self): + return self._lowered.__hash__() - def __lt__(self, other): - if isinstance(other, Identifier): - return self._lowered < other._lowered - return self._lowered < Identifier._lower(other) + def __lt__(self, other): + if isinstance(other, Identifier): + return self._lowered < other._lowered + return self._lowered < Identifier._lower(other) - def __le__(self, other): - if isinstance(other, Identifier): - return self._lowered <= other._lowered - return self._lowered <= Identifier._lower(other) + def __le__(self, other): + if isinstance(other, Identifier): + return self._lowered <= other._lowered + return self._lowered <= Identifier._lower(other) - def __gt__(self, other): - if isinstance(other, Identifier): - return self._lowered > other._lowered - return self._lowered > Identifier._lower(other) + def __gt__(self, other): + if isinstance(other, Identifier): + return self._lowered > other._lowered + return self._lowered > Identifier._lower(other) - def __ge__(self, other): - if isinstance(other, Identifier): - return self._lowered >= other._lowered - return self._lowered >= Identifier._lower(other) + def __ge__(self, other): + if isinstance(other, Identifier): + return self._lowered >= other._lowered + return self._lowered >= Identifier._lower(other) - def __eq__(self, other): - if isinstance(other, Identifier): - return self._lowered == other._lowered - return self._lowered == Identifier._lower(other) + def __eq__(self, other): + if isinstance(other, Identifier): + return self._lowered == other._lowered + return self._lowered == Identifier._lower(other) - def __ne__(self, other): - return not (self == other) + def __ne__(self, other): + return not (self == other) - def is_nick(self): - """Returns True if the Identifier is a nickname (as opposed to channel) - """ - return self and not self.startswith(_channel_prefixes) + def is_nick(self): + """Returns True if the Identifier is a nickname (as opposed to channel) + """ + return self and not self.startswith(_channel_prefixes) class OutputRedirect(object): - """Redirect te output to the terminal and a log file. + """Redirect te output to the terminal and a log file. - A simplified object used to write to both the terminal and a log file. + A simplified object used to write to both the terminal and a log file. - """ + """ - def __init__(self, logpath, stderr=False, quiet=False): - """Create an object which will to to a file and the terminal. + def __init__(self, logpath, stderr=False, quiet=False): + """Create an object which will to to a file and the terminal. - Create an object which will log to the file at ``logpath`` as well as - the terminal. - If ``stderr`` is given and true, it will write to stderr rather than - stdout. - If ``quiet`` is given and True, data will be written to the log file - only, but not the terminal. + Create an object which will log to the file at ``logpath`` as well as + the terminal. + If ``stderr`` is given and true, it will write to stderr rather than + stdout. + If ``quiet`` is given and True, data will be written to the log file + only, but not the terminal. - """ - self.logpath = logpath - self.stderr = stderr - self.quiet = quiet + """ + self.logpath = logpath + self.stderr = stderr + self.quiet = quiet - def write(self, string): - """Write the given ``string`` to the logfile and terminal.""" - if not self.quiet: - try: - if self.stderr: - sys.__stderr__.write(string) - else: - sys.__stdout__.write(string) - except: - pass + def write(self, string): + """Write the given ``string`` to the logfile and terminal.""" + if not self.quiet: + try: + if self.stderr: + sys.__stderr__.write(string) + else: + sys.__stdout__.write(string) + except: + pass - with codecs.open(self.logpath, 'ab', encoding="utf8", - errors='xmlcharrefreplace') as logfile: - try: - logfile.write(string) - except UnicodeDecodeError: - # we got an invalid string, safely encode it to utf-8 - logfile.write(unicode(string, 'utf8', errors="replace")) + with codecs.open(self.logpath, 'ab', encoding="utf8", + errors='xmlcharrefreplace') as logfile: + try: + logfile.write(string) + except strDecodeError: + # we got an invalid string, safely encode it to utf-8 + logfile.write(str(string, 'utf8', errors="replace")) - def flush(self): - if self.stderr: - sys.__stderr__.flush() - else: - sys.__stdout__.flush() + def flush(self): + if self.stderr: + sys.__stderr__.flush() + else: + sys.__stdout__.flush() # These seems to trace back to when we thought we needed a try/except on prints, @@ -252,101 +233,101 @@ class OutputRedirect(object): # 4.0^H^H^H5.0^H^H^H6.0^H^H^Hsome version when someone can be bothered. @deprecated def stdout(string): - print(string) + print(string) def stderr(string): - """Print the given ``string`` to stderr. + """Print the given ``string`` to stderr. - This is equivalent to ``print >> sys.stderr, string`` + This is equivalent to ``print >> sys.stderr, string`` - """ - print(string, file=sys.stderr) + """ + print(string, file=sys.stderr) def check_pid(pid): - """Check if a process is running with the given ``PID``. + """Check if a process is running with the given ``PID``. - *Availability: Only on POSIX systems* + *Availability: Only on POSIX systems* - Return ``True`` if there is a process running with the given ``PID``. + Return ``True`` if there is a process running with the given ``PID``. - """ - try: - os.kill(pid, 0) - except OSError: - return False - else: - return True + """ + try: + os.kill(pid, 0) + except OSError: + return False + else: + return True def get_hostmask_regex(mask): - """Return a compiled `re.RegexObject` for an IRC hostmask""" - mask = re.escape(mask) - mask = mask.replace(r'\*', '.*') - return re.compile(mask + '$', re.I) + """Return a compiled `re.RegexObject` for an IRC hostmask""" + mask = re.escape(mask) + mask = mask.replace(r'\*', '.*') + return re.compile(mask + '$', re.I) class SopelMemory(dict): - """A simple thread-safe dict implementation. + """A simple thread-safe dict implementation. - *Availability: 4.0; available as ``Sopel.SopelMemory`` in 3.1.0 - 3.2.0* + *Availability: 4.0; available as ``Sopel.SopelMemory`` in 3.1.0 - 3.2.0* - In order to prevent exceptions when iterating over the values and changing - them at the same time from different threads, we use a blocking lock on - ``__setitem__`` and ``contains``. + In order to prevent exceptions when iterating over the values and changing + them at the same time from different threads, we use a blocking lock on + ``__setitem__`` and ``contains``. - """ - def __init__(self, *args): - dict.__init__(self, *args) - self.lock = threading.Lock() + """ + def __init__(self, *args): + dict.__init__(self, *args) + self.lock = threading.Lock() - def __setitem__(self, key, value): - self.lock.acquire() - result = dict.__setitem__(self, key, value) - self.lock.release() - return result + def __setitem__(self, key, value): + self.lock.acquire() + result = dict.__setitem__(self, key, value) + self.lock.release() + return result - def __contains__(self, key): - """Check if a key is in the dict. + def __contains__(self, key): + """Check if a key is in the dict. - It locks it for writes when doing so. + It locks it for writes when doing so. - """ - self.lock.acquire() - result = dict.__contains__(self, key) - self.lock.release() - return result + """ + self.lock.acquire() + result = dict.__contains__(self, key) + self.lock.release() + return result - def contains(self, key): - """Backwards compatability with 3.x, use `in` operator instead.""" - return self.__contains__(key) + def contains(self, key): + """Backwards compatability with 3.x, use `in` operator instead.""" + return self.__contains__(key) class SopelMemoryWithDefault(defaultdict): - """Same as SopelMemory, but subclasses from collections.defaultdict.""" - def __init__(self, *args): - defaultdict.__init__(self, *args) - self.lock = threading.Lock() + """Same as SopelMemory, but subclasses from collections.defaultdict.""" + def __init__(self, *args): + defaultdict.__init__(self, *args) + self.lock = threading.Lock() - def __setitem__(self, key, value): - self.lock.acquire() - result = defaultdict.__setitem__(self, key, value) - self.lock.release() - return result + def __setitem__(self, key, value): + self.lock.acquire() + result = defaultdict.__setitem__(self, key, value) + self.lock.release() + return result - def __contains__(self, key): - """Check if a key is in the dict. + def __contains__(self, key): + """Check if a key is in the dict. - It locks it for writes when doing so. + It locks it for writes when doing so. - """ - self.lock.acquire() - result = defaultdict.__contains__(self, key) - self.lock.release() - return result + """ + self.lock.acquire() + result = defaultdict.__contains__(self, key) + self.lock.release() + return result - def contains(self, key): - """Backwards compatability with 3.x, use `in` operator instead.""" - return self.__contains__(key) + def contains(self, key): + """Backwards compatability with 3.x, use `in` operator instead.""" + return self.__contains__(key)