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