i need a rundown, stat

This commit is contained in:
iou1name 2018-01-18 23:43:27 -05:00
parent e6e8d544d2
commit 5b1909cac5
10 changed files with 446 additions and 452 deletions

2
bot.py
View File

@ -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.

View File

@ -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 <elad@fedoraproject.org>
# 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!")

4
irc.py
View File

@ -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))

View File

@ -1,5 +1,4 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
"""
Methods for loading modules.
"""

View File

@ -1,4 +1,3 @@
# coding=utf-8
"""This contains decorators and tools for creating callable plugin functions.
"""
# Copyright 2013, Ari Koivula, <ari@koivu.la>

View File

@ -1,5 +1,4 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
"""
ASCII
"""

View File

@ -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

View File

@ -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)

23
modules/rundown.py Executable file
View File

@ -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)

View File

@ -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 <elad@fedoraproject.org>
# 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)