split some stuff out of utils/__init__.py
This commit is contained in:
parent
6a55b14afa
commit
bfcf40edd7
5 changed files with 192 additions and 182 deletions
|
@ -97,7 +97,7 @@ class Module(ModuleManager.BaseModule):
|
||||||
return "{0:,.2f}".format(coins)
|
return "{0:,.2f}".format(coins)
|
||||||
def _parse_coins(self, s, minimum=None):
|
def _parse_coins(self, s, minimum=None):
|
||||||
try:
|
try:
|
||||||
s = utils.parse_number(s)
|
s = utils.parse.parse_number(s)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,10 @@ import contextlib, datetime, decimal, enum, io, ipaddress, multiprocessing
|
||||||
import queue, re, signal, threading, typing
|
import queue, re, signal, threading, typing
|
||||||
from . import cli, consts, irc, http, parse, security
|
from . import cli, consts, irc, http, parse, security
|
||||||
|
|
||||||
|
from .decorators import export, get_magic, has_magic, hook, kwarg
|
||||||
|
from .settings import (BoolSetting, FunctionSetting, IntRangeSetting,
|
||||||
|
IntSetting, OptionsSetting, sensitive_format, SensitiveSetting, Setting)
|
||||||
|
|
||||||
class Direction(enum.Enum):
|
class Direction(enum.Enum):
|
||||||
Send = 0
|
Send = 0
|
||||||
Recv = 1
|
Recv = 1
|
||||||
|
@ -129,31 +133,6 @@ def to_pretty_time(total_seconds: int, minimum_unit: int=UNIT_SECOND,
|
||||||
units += 1
|
units += 1
|
||||||
return " ".join(out)
|
return " ".join(out)
|
||||||
|
|
||||||
def parse_number(s: str) -> str:
|
|
||||||
try:
|
|
||||||
decimal.Decimal(s)
|
|
||||||
return s
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
unit = s[-1].lower()
|
|
||||||
number_str = s[:-1]
|
|
||||||
try:
|
|
||||||
number = decimal.Decimal(number_str)
|
|
||||||
except:
|
|
||||||
raise ValueError("Invalid format '%s' passed to parse_number" %
|
|
||||||
number_str)
|
|
||||||
|
|
||||||
if unit == "k":
|
|
||||||
number *= decimal.Decimal("1_000")
|
|
||||||
elif unit == "m":
|
|
||||||
number *= decimal.Decimal("1_000_000")
|
|
||||||
elif unit == "b":
|
|
||||||
number *= decimal.Decimal("1_000_000_000")
|
|
||||||
else:
|
|
||||||
raise ValueError("Unknown unit '%s' given to parse_number" % unit)
|
|
||||||
return str(number)
|
|
||||||
|
|
||||||
def prevent_highlight(nickname: str) -> str:
|
def prevent_highlight(nickname: str) -> str:
|
||||||
return nickname[0]+"\u200c"+nickname[1:]
|
return nickname[0]+"\u200c"+nickname[1:]
|
||||||
|
|
||||||
|
@ -169,53 +148,6 @@ class EventsUsageError(EventError):
|
||||||
def __init__(self, usage):
|
def __init__(self, usage):
|
||||||
EventError.__init__(self, "Not enough arguments, usage: %s" % usage)
|
EventError.__init__(self, "Not enough arguments, usage: %s" % usage)
|
||||||
|
|
||||||
class BitBotMagic(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._hooks: typing.List[typing.Tuple[str, dict]] = []
|
|
||||||
self._kwargs: typing.List[typing.Tuple[str, typing.Any]] = []
|
|
||||||
self._exports: typing.List[typing.Tuple[str, typing.Any]] = []
|
|
||||||
def add_hook(self, hook: str, kwargs: dict):
|
|
||||||
self._hooks.append((hook, kwargs))
|
|
||||||
def add_kwarg(self, key: str, value: typing.Any):
|
|
||||||
self._kwargs.append((key, value))
|
|
||||||
|
|
||||||
def get_hooks(self):
|
|
||||||
hooks: typing.List[typing.Tuple[str, typing.List[Tuple[str, typing.Any]]]] = []
|
|
||||||
for hook, kwargs in self._hooks:
|
|
||||||
hooks.append((hook, self._kwargs.copy()+list(kwargs.items())))
|
|
||||||
return hooks
|
|
||||||
|
|
||||||
def add_export(self, key: str, value: typing.Any):
|
|
||||||
self._exports.append((key, value))
|
|
||||||
def get_exports(self):
|
|
||||||
return self._exports.copy()
|
|
||||||
|
|
||||||
def get_magic(obj: typing.Any):
|
|
||||||
if not has_magic(obj):
|
|
||||||
setattr(obj, consts.BITBOT_MAGIC, BitBotMagic())
|
|
||||||
return getattr(obj, consts.BITBOT_MAGIC)
|
|
||||||
def has_magic(obj: typing.Any):
|
|
||||||
return hasattr(obj, consts.BITBOT_MAGIC)
|
|
||||||
|
|
||||||
def hook(event: str, **kwargs):
|
|
||||||
def _hook_func(func):
|
|
||||||
magic = get_magic(func)
|
|
||||||
magic.add_hook(event, kwargs)
|
|
||||||
return func
|
|
||||||
return _hook_func
|
|
||||||
def export(setting: str, value: typing.Any):
|
|
||||||
def _export_func(module):
|
|
||||||
magic = get_magic(module)
|
|
||||||
magic.add_export(setting, value)
|
|
||||||
return module
|
|
||||||
return _export_func
|
|
||||||
def kwarg(key: str, value: typing.Any):
|
|
||||||
def _kwarg_func(func):
|
|
||||||
magic = get_magic(func)
|
|
||||||
magic.add_kwarg(key, value)
|
|
||||||
return func
|
|
||||||
return _kwarg_func
|
|
||||||
|
|
||||||
class MultiCheck(object):
|
class MultiCheck(object):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
requests: typing.List[typing.Tuple[str, typing.List[str]]]):
|
requests: typing.List[typing.Tuple[str, typing.List[str]]]):
|
||||||
|
@ -275,114 +207,6 @@ def is_ip(s: str) -> bool:
|
||||||
def is_main_thread() -> bool:
|
def is_main_thread() -> bool:
|
||||||
return threading.current_thread() is threading.main_thread()
|
return threading.current_thread() is threading.main_thread()
|
||||||
|
|
||||||
class SettingParseException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Setting(object):
|
|
||||||
example: typing.Optional[str] = None
|
|
||||||
def __init__(self, name: str, help: str=None, example: str=None):
|
|
||||||
self.name = name
|
|
||||||
self.help = help
|
|
||||||
if not example == None:
|
|
||||||
self.example = example
|
|
||||||
def parse(self, value: str) -> typing.Any:
|
|
||||||
return value
|
|
||||||
|
|
||||||
def get_example(self):
|
|
||||||
if not self.example == None:
|
|
||||||
return "Example: %s" % self.example
|
|
||||||
else:
|
|
||||||
return self._format_example()
|
|
||||||
def _format_example(self):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def format(self, value: typing.Any):
|
|
||||||
return repr(value)
|
|
||||||
|
|
||||||
SETTING_TRUE = ["true", "yes", "on", "y"]
|
|
||||||
SETTING_FALSE = ["false", "no", "off", "n"]
|
|
||||||
class BoolSetting(Setting):
|
|
||||||
example: typing.Optional[str] = "on"
|
|
||||||
def parse(self, value: str) -> typing.Any:
|
|
||||||
value_lower = value.lower()
|
|
||||||
if value_lower in SETTING_TRUE:
|
|
||||||
return True
|
|
||||||
elif value_lower in SETTING_FALSE:
|
|
||||||
return False
|
|
||||||
return None
|
|
||||||
|
|
||||||
class IntSetting(Setting):
|
|
||||||
example: typing.Optional[str] = "10"
|
|
||||||
def parse(self, value: str) -> typing.Any:
|
|
||||||
if value == "0":
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
stripped = value.lstrip("0")
|
|
||||||
if stripped.isdigit():
|
|
||||||
return int(stripped)
|
|
||||||
return None
|
|
||||||
|
|
||||||
class IntRangeSetting(IntSetting):
|
|
||||||
example: typing.Optional[str] = None
|
|
||||||
def __init__(self, n_min: int, n_max: int, name: str, help: str=None,
|
|
||||||
example: str=None):
|
|
||||||
self._n_min = n_min
|
|
||||||
self._n_max = n_max
|
|
||||||
Setting.__init__(self, name, help, example)
|
|
||||||
|
|
||||||
def parse(self, value: str) -> typing.Any:
|
|
||||||
out = IntSetting.parse(self, value)
|
|
||||||
if not out == None and self._n_min <= out <= self._n_max:
|
|
||||||
return out
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _format_example(self):
|
|
||||||
return "Must be between %d and %d" % (self._n_min, self._n_max)
|
|
||||||
|
|
||||||
class OptionsSetting(Setting):
|
|
||||||
def __init__(self, options: typing.List[str], name: str, help: str=None,
|
|
||||||
example: str=None,
|
|
||||||
options_factory: typing.Callable[[], typing.List[str]]=None):
|
|
||||||
self._options = options
|
|
||||||
self._options_factory = options_factory
|
|
||||||
Setting.__init__(self, name, help, example)
|
|
||||||
|
|
||||||
def _get_options(self):
|
|
||||||
if not self._options_factory == None:
|
|
||||||
return self._options_factory()
|
|
||||||
else:
|
|
||||||
return self._options
|
|
||||||
|
|
||||||
def parse(self, value: str) -> typing.Any:
|
|
||||||
value_lower = value.lower()
|
|
||||||
for option in self._get_options():
|
|
||||||
if option.lower() == value_lower:
|
|
||||||
return option
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _format_example(self):
|
|
||||||
options = self._get_options()
|
|
||||||
options_str = ["'%s'" % option for option in options]
|
|
||||||
return "Options: %s" % ", ".join(options_str)
|
|
||||||
|
|
||||||
class FunctionSetting(Setting):
|
|
||||||
def __init__(self, func: typing.Callable[[str], bool], name: str,
|
|
||||||
help: str=None, example: str=None, format=None):
|
|
||||||
self._func = func
|
|
||||||
Setting.__init__(self, name, help, example)
|
|
||||||
if not format == None:
|
|
||||||
self.format = format # type: ignore
|
|
||||||
|
|
||||||
def parse(self, value: str) -> typing.Any:
|
|
||||||
return self._func(value)
|
|
||||||
|
|
||||||
def sensitive_format(value: typing.Any):
|
|
||||||
return "*"*16
|
|
||||||
|
|
||||||
class SensitiveSetting(Setting):
|
|
||||||
def format(self, value: typing.Any):
|
|
||||||
return sensitive_format(value)
|
|
||||||
|
|
||||||
class DeadlineExceededException(Exception):
|
class DeadlineExceededException(Exception):
|
||||||
pass
|
pass
|
||||||
def _raise_deadline():
|
def _raise_deadline():
|
||||||
|
|
50
src/utils/decorators.py
Normal file
50
src/utils/decorators.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import typing
|
||||||
|
from . import consts
|
||||||
|
|
||||||
|
class BitBotMagic(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._hooks: typing.List[typing.Tuple[str, dict]] = []
|
||||||
|
self._kwargs: typing.List[typing.Tuple[str, typing.Any]] = []
|
||||||
|
self._exports: typing.List[typing.Tuple[str, typing.Any]] = []
|
||||||
|
def add_hook(self, hook: str, kwargs: dict):
|
||||||
|
self._hooks.append((hook, kwargs))
|
||||||
|
def add_kwarg(self, key: str, value: typing.Any):
|
||||||
|
self._kwargs.append((key, value))
|
||||||
|
|
||||||
|
def get_hooks(self):
|
||||||
|
hooks: typing.List[typing.Tuple[str, typing.List[Tuple[str, typing.Any]]]] = []
|
||||||
|
for hook, kwargs in self._hooks:
|
||||||
|
hooks.append((hook, self._kwargs.copy()+list(kwargs.items())))
|
||||||
|
return hooks
|
||||||
|
|
||||||
|
def add_export(self, key: str, value: typing.Any):
|
||||||
|
self._exports.append((key, value))
|
||||||
|
def get_exports(self):
|
||||||
|
return self._exports.copy()
|
||||||
|
|
||||||
|
def get_magic(obj: typing.Any):
|
||||||
|
if not has_magic(obj):
|
||||||
|
setattr(obj, consts.BITBOT_MAGIC, BitBotMagic())
|
||||||
|
return getattr(obj, consts.BITBOT_MAGIC)
|
||||||
|
def has_magic(obj: typing.Any):
|
||||||
|
return hasattr(obj, consts.BITBOT_MAGIC)
|
||||||
|
|
||||||
|
def hook(event: str, **kwargs):
|
||||||
|
def _hook_func(func):
|
||||||
|
magic = get_magic(func)
|
||||||
|
magic.add_hook(event, kwargs)
|
||||||
|
return func
|
||||||
|
return _hook_func
|
||||||
|
def export(setting: str, value: typing.Any):
|
||||||
|
def _export_func(module):
|
||||||
|
magic = get_magic(module)
|
||||||
|
magic.add_export(setting, value)
|
||||||
|
return module
|
||||||
|
return _export_func
|
||||||
|
def kwarg(key: str, value: typing.Any):
|
||||||
|
def _kwarg_func(func):
|
||||||
|
magic = get_magic(func)
|
||||||
|
magic.add_kwarg(key, value)
|
||||||
|
return func
|
||||||
|
return _kwarg_func
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import io, typing
|
import decimal, io, typing
|
||||||
|
|
||||||
COMMENT_TYPES = ["#", "//"]
|
COMMENT_TYPES = ["#", "//"]
|
||||||
def hashflags(filename: str
|
def hashflags(filename: str
|
||||||
|
@ -83,3 +83,29 @@ def try_int(s: str) -> typing.Optional[int]:
|
||||||
def line_normalise(s: str) -> str:
|
def line_normalise(s: str) -> str:
|
||||||
lines = list(filter(None, [line.strip() for line in s.split("\n")]))
|
lines = list(filter(None, [line.strip() for line in s.split("\n")]))
|
||||||
return " ".join(line.replace(" ", " ") for line in lines)
|
return " ".join(line.replace(" ", " ") for line in lines)
|
||||||
|
|
||||||
|
def parse_number(s: str) -> str:
|
||||||
|
try:
|
||||||
|
decimal.Decimal(s)
|
||||||
|
return s
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
unit = s[-1].lower()
|
||||||
|
number_str = s[:-1]
|
||||||
|
try:
|
||||||
|
number = decimal.Decimal(number_str)
|
||||||
|
except:
|
||||||
|
raise ValueError("Invalid format '%s' passed to parse_number" %
|
||||||
|
number_str)
|
||||||
|
|
||||||
|
if unit == "k":
|
||||||
|
number *= decimal.Decimal("1_000")
|
||||||
|
elif unit == "m":
|
||||||
|
number *= decimal.Decimal("1_000_000")
|
||||||
|
elif unit == "b":
|
||||||
|
number *= decimal.Decimal("1_000_000_000")
|
||||||
|
else:
|
||||||
|
raise ValueError("Unknown unit '%s' given to parse_number" % unit)
|
||||||
|
return str(number)
|
||||||
|
|
||||||
|
|
110
src/utils/settings.py
Normal file
110
src/utils/settings.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import typing
|
||||||
|
|
||||||
|
class SettingParseException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Setting(object):
|
||||||
|
example: typing.Optional[str] = None
|
||||||
|
def __init__(self, name: str, help: str=None, example: str=None):
|
||||||
|
self.name = name
|
||||||
|
self.help = help
|
||||||
|
if not example == None:
|
||||||
|
self.example = example
|
||||||
|
def parse(self, value: str) -> typing.Any:
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_example(self):
|
||||||
|
if not self.example == None:
|
||||||
|
return "Example: %s" % self.example
|
||||||
|
else:
|
||||||
|
return self._format_example()
|
||||||
|
def _format_example(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def format(self, value: typing.Any):
|
||||||
|
return repr(value)
|
||||||
|
|
||||||
|
SETTING_TRUE = ["true", "yes", "on", "y"]
|
||||||
|
SETTING_FALSE = ["false", "no", "off", "n"]
|
||||||
|
class BoolSetting(Setting):
|
||||||
|
example: typing.Optional[str] = "on"
|
||||||
|
def parse(self, value: str) -> typing.Any:
|
||||||
|
value_lower = value.lower()
|
||||||
|
if value_lower in SETTING_TRUE:
|
||||||
|
return True
|
||||||
|
elif value_lower in SETTING_FALSE:
|
||||||
|
return False
|
||||||
|
return None
|
||||||
|
|
||||||
|
class IntSetting(Setting):
|
||||||
|
example: typing.Optional[str] = "10"
|
||||||
|
def parse(self, value: str) -> typing.Any:
|
||||||
|
if value == "0":
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
stripped = value.lstrip("0")
|
||||||
|
if stripped.isdigit():
|
||||||
|
return int(stripped)
|
||||||
|
return None
|
||||||
|
|
||||||
|
class IntRangeSetting(IntSetting):
|
||||||
|
example: typing.Optional[str] = None
|
||||||
|
def __init__(self, n_min: int, n_max: int, name: str, help: str=None,
|
||||||
|
example: str=None):
|
||||||
|
self._n_min = n_min
|
||||||
|
self._n_max = n_max
|
||||||
|
Setting.__init__(self, name, help, example)
|
||||||
|
|
||||||
|
def parse(self, value: str) -> typing.Any:
|
||||||
|
out = IntSetting.parse(self, value)
|
||||||
|
if not out == None and self._n_min <= out <= self._n_max:
|
||||||
|
return out
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _format_example(self):
|
||||||
|
return "Must be between %d and %d" % (self._n_min, self._n_max)
|
||||||
|
|
||||||
|
class OptionsSetting(Setting):
|
||||||
|
def __init__(self, options: typing.List[str], name: str, help: str=None,
|
||||||
|
example: str=None,
|
||||||
|
options_factory: typing.Callable[[], typing.List[str]]=None):
|
||||||
|
self._options = options
|
||||||
|
self._options_factory = options_factory
|
||||||
|
Setting.__init__(self, name, help, example)
|
||||||
|
|
||||||
|
def _get_options(self):
|
||||||
|
if not self._options_factory == None:
|
||||||
|
return self._options_factory()
|
||||||
|
else:
|
||||||
|
return self._options
|
||||||
|
|
||||||
|
def parse(self, value: str) -> typing.Any:
|
||||||
|
value_lower = value.lower()
|
||||||
|
for option in self._get_options():
|
||||||
|
if option.lower() == value_lower:
|
||||||
|
return option
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _format_example(self):
|
||||||
|
options = self._get_options()
|
||||||
|
options_str = ["'%s'" % option for option in options]
|
||||||
|
return "Options: %s" % ", ".join(options_str)
|
||||||
|
|
||||||
|
class FunctionSetting(Setting):
|
||||||
|
def __init__(self, func: typing.Callable[[str], bool], name: str,
|
||||||
|
help: str=None, example: str=None, format=None):
|
||||||
|
self._func = func
|
||||||
|
Setting.__init__(self, name, help, example)
|
||||||
|
if not format == None:
|
||||||
|
self.format = format # type: ignore
|
||||||
|
|
||||||
|
def parse(self, value: str) -> typing.Any:
|
||||||
|
return self._func(value)
|
||||||
|
|
||||||
|
def sensitive_format(value: typing.Any):
|
||||||
|
return "*"*16
|
||||||
|
|
||||||
|
class SensitiveSetting(Setting):
|
||||||
|
def format(self, value: typing.Any):
|
||||||
|
return sensitive_format(value)
|
||||||
|
|
Loading…
Reference in a new issue