From bfcf40edd71c5bdd436ab8b535c880fad8781f16 Mon Sep 17 00:00:00 2001 From: jesopo Date: Fri, 15 Nov 2019 13:39:24 +0000 Subject: [PATCH] split some stuff out of utils/__init__.py --- modules/coins.py | 2 +- src/utils/__init__.py | 184 +--------------------------------------- src/utils/decorators.py | 50 +++++++++++ src/utils/parse.py | 28 +++++- src/utils/settings.py | 110 ++++++++++++++++++++++++ 5 files changed, 192 insertions(+), 182 deletions(-) create mode 100644 src/utils/decorators.py create mode 100644 src/utils/settings.py diff --git a/modules/coins.py b/modules/coins.py index 413714d2..dc2a240a 100644 --- a/modules/coins.py +++ b/modules/coins.py @@ -97,7 +97,7 @@ class Module(ModuleManager.BaseModule): return "{0:,.2f}".format(coins) def _parse_coins(self, s, minimum=None): try: - s = utils.parse_number(s) + s = utils.parse.parse_number(s) except ValueError: pass diff --git a/src/utils/__init__.py b/src/utils/__init__.py index 33ad449b..1013b2ac 100644 --- a/src/utils/__init__.py +++ b/src/utils/__init__.py @@ -2,6 +2,10 @@ import contextlib, datetime, decimal, enum, io, ipaddress, multiprocessing import queue, re, signal, threading, typing 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): Send = 0 Recv = 1 @@ -129,31 +133,6 @@ def to_pretty_time(total_seconds: int, minimum_unit: int=UNIT_SECOND, units += 1 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: return nickname[0]+"\u200c"+nickname[1:] @@ -169,53 +148,6 @@ class EventsUsageError(EventError): def __init__(self, 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): def __init__(self, requests: typing.List[typing.Tuple[str, typing.List[str]]]): @@ -275,114 +207,6 @@ def is_ip(s: str) -> bool: def is_main_thread() -> bool: 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): pass def _raise_deadline(): diff --git a/src/utils/decorators.py b/src/utils/decorators.py new file mode 100644 index 00000000..f6897e17 --- /dev/null +++ b/src/utils/decorators.py @@ -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 + diff --git a/src/utils/parse.py b/src/utils/parse.py index 7699bd19..d5018441 100644 --- a/src/utils/parse.py +++ b/src/utils/parse.py @@ -1,4 +1,4 @@ -import io, typing +import decimal, io, typing COMMENT_TYPES = ["#", "//"] def hashflags(filename: str @@ -83,3 +83,29 @@ def try_int(s: str) -> typing.Optional[int]: def line_normalise(s: str) -> str: lines = list(filter(None, [line.strip() for line in s.split("\n")])) 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) + diff --git a/src/utils/settings.py b/src/utils/settings.py new file mode 100644 index 00000000..8040c2e8 --- /dev/null +++ b/src/utils/settings.py @@ -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) +