Added black formatter to pre-commit hook
parent
6f2d9d1dde
commit
7a7bd6fed4
|
@ -3,8 +3,8 @@
|
||||||
# This hooks runs tests and checks if the linter doesn't hate you
|
# This hooks runs tests and checks if the linter doesn't hate you
|
||||||
|
|
||||||
# Linting
|
# Linting
|
||||||
printf "flake8....."
|
printf "black......"
|
||||||
make lint > /dev/null 2>&1 && printf "OK!\n" || { printf "Fail." && exit 1; }
|
make format > /dev/null 2>&1 && printf "OK!\n" || { printf "Fail." && exit 1; }
|
||||||
|
|
||||||
# Running tests
|
# Running tests
|
||||||
printf "pytest....."
|
printf "pytest....."
|
||||||
|
|
5
Makefile
5
Makefile
|
@ -69,6 +69,11 @@ test: pytest.ini build-venv
|
||||||
lint: build-venv
|
lint: build-venv
|
||||||
@ "$(VENV)/bin/flake8" "$(SRC)"/**/*.py
|
@ "$(VENV)/bin/flake8" "$(SRC)"/**/*.py
|
||||||
|
|
||||||
|
# =====FORMATTING=====
|
||||||
|
# Run black
|
||||||
|
format: build-venv
|
||||||
|
@ "$(VENV)/bin/black" '$(SRC)'
|
||||||
|
|
||||||
|
|
||||||
# =====PACKAGING=====
|
# =====PACKAGING=====
|
||||||
package: README.md LICENSE setup.py test clean-setup
|
package: README.md LICENSE setup.py test clean-setup
|
||||||
|
|
|
@ -1,18 +1,25 @@
|
||||||
from .frank import Frank
|
from .frank import Frank
|
||||||
from .module import (
|
from .module import (
|
||||||
Module, command, Command, default, Default, daemon, Daemon,
|
Module,
|
||||||
regex_command, RegexCommand,
|
command,
|
||||||
|
Command,
|
||||||
|
default,
|
||||||
|
Default,
|
||||||
|
daemon,
|
||||||
|
Daemon,
|
||||||
|
regex_command,
|
||||||
|
RegexCommand,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Frank',
|
"Frank",
|
||||||
'Module',
|
"Module",
|
||||||
'command',
|
"command",
|
||||||
'Command',
|
"Command",
|
||||||
'default',
|
"default",
|
||||||
'Default',
|
"Default",
|
||||||
'daemon',
|
"daemon",
|
||||||
'Daemon',
|
"Daemon",
|
||||||
'regex_command',
|
"regex_command",
|
||||||
'RegexCommand',
|
"RegexCommand",
|
||||||
]
|
]
|
||||||
|
|
|
@ -11,6 +11,7 @@ import discord
|
||||||
|
|
||||||
# Typing imports
|
# Typing imports
|
||||||
from typing import TYPE_CHECKING, List
|
from typing import TYPE_CHECKING, List
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# Own imports
|
# Own imports
|
||||||
from .module import Module
|
from .module import Module
|
||||||
|
@ -23,8 +24,12 @@ class Frank(discord.Client):
|
||||||
their own behavior.
|
their own behavior.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, modules: List[Module], config_file: str = 'frank.yaml',
|
def __init__(
|
||||||
prefix: str = 'fr'):
|
self,
|
||||||
|
modules: List[Module],
|
||||||
|
config_file: str = "frank.yaml",
|
||||||
|
prefix: str = "fr",
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
modules: modules to load
|
modules: modules to load
|
||||||
|
@ -39,7 +44,7 @@ class Frank(discord.Client):
|
||||||
self.PREFIX = prefix
|
self.PREFIX = prefix
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(config_file, 'r') as f:
|
with open(config_file, "r") as f:
|
||||||
self._config = yaml.load(f, Loader=yaml.FullLoader)
|
self._config = yaml.load(f, Loader=yaml.FullLoader)
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
@ -50,7 +55,7 @@ class Frank(discord.Client):
|
||||||
Runs when the bot has succesfully connected to Discord
|
Runs when the bot has succesfully connected to Discord
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print('Connected')
|
print("Connected")
|
||||||
|
|
||||||
# Startup all modules
|
# Startup all modules
|
||||||
for module in self._modules:
|
for module in self._modules:
|
||||||
|
@ -63,7 +68,7 @@ class Frank(discord.Client):
|
||||||
await loaded._start()
|
await loaded._start()
|
||||||
self._loaded_modules.append(loaded)
|
self._loaded_modules.append(loaded)
|
||||||
|
|
||||||
print('All modules loaded')
|
print("All modules loaded")
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self):
|
||||||
"""
|
"""
|
||||||
|
@ -92,9 +97,14 @@ class Frank(discord.Client):
|
||||||
if not (cmd and cmd[0] == self.PREFIX):
|
if not (cmd and cmd[0] == self.PREFIX):
|
||||||
return
|
return
|
||||||
|
|
||||||
module = next((mod for mod in self._loaded_modules
|
module = next(
|
||||||
if mod.match(cmd[1])), None)
|
(mod for mod in self._loaded_modules if mod.match(cmd[1])), None
|
||||||
|
)
|
||||||
|
|
||||||
if module:
|
if module:
|
||||||
await module(cmd=cmd[2:], author=message.author,
|
await module(
|
||||||
channel=message.channel, mid=message.id)
|
cmd=cmd[2:],
|
||||||
|
author=message.author,
|
||||||
|
channel=message.channel,
|
||||||
|
mid=message.id,
|
||||||
|
)
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
from .module import Module
|
from .module import Module
|
||||||
from .decorators import (
|
from .decorators import (
|
||||||
command, Command, default, Default, daemon, Daemon, regex_command,
|
command,
|
||||||
|
Command,
|
||||||
|
default,
|
||||||
|
Default,
|
||||||
|
daemon,
|
||||||
|
Daemon,
|
||||||
|
regex_command,
|
||||||
RegexCommand,
|
RegexCommand,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Module',
|
"Module",
|
||||||
'command',
|
"command",
|
||||||
'Command',
|
"Command",
|
||||||
'default',
|
"default",
|
||||||
'Default',
|
"Default",
|
||||||
'daemon',
|
"daemon",
|
||||||
'Daemon',
|
"Daemon",
|
||||||
'regex_command',
|
"regex_command",
|
||||||
'RegexCommand',
|
"RegexCommand",
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,12 +2,12 @@ from .classes import Command, RegexCommand, Daemon, Default
|
||||||
from .functions import command, regex_command, daemon, default
|
from .functions import command, regex_command, daemon, default
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'command',
|
"command",
|
||||||
'Command',
|
"Command",
|
||||||
'regex_command',
|
"regex_command",
|
||||||
'RegexCommand',
|
"RegexCommand",
|
||||||
'default',
|
"default",
|
||||||
'Default',
|
"Default",
|
||||||
'daemon',
|
"daemon",
|
||||||
'Daemon',
|
"Daemon",
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,6 +8,7 @@ import asyncio
|
||||||
|
|
||||||
# Typing imports
|
# Typing imports
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# Built-in imports
|
# Built-in imports
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
@ -120,6 +121,7 @@ class Daemon(Simple):
|
||||||
# If an interval > 0 is given, it wraps the function inside an infinite
|
# If an interval > 0 is given, it wraps the function inside an infinite
|
||||||
# loop with the desired delay
|
# loop with the desired delay
|
||||||
if interval > 0:
|
if interval > 0:
|
||||||
|
|
||||||
async def loop(self, *args, **kwargs):
|
async def loop(self, *args, **kwargs):
|
||||||
while True:
|
while True:
|
||||||
# TODO: does this make func and sleep run at the same time?
|
# TODO: does this make func and sleep run at the same time?
|
||||||
|
|
|
@ -7,6 +7,7 @@ from .classes import Command, RegexCommand, Daemon, Default
|
||||||
|
|
||||||
# Typing imports
|
# Typing imports
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# Built-in imports
|
# Built-in imports
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
|
@ -7,4 +7,4 @@ class DuplicateCommand(Exception):
|
||||||
|
|
||||||
|
|
||||||
class MultipleDefaults(Exception):
|
class MultipleDefaults(Exception):
|
||||||
message = 'Multiple default commands detected'
|
message = "Multiple default commands detected"
|
||||||
|
|
|
@ -10,6 +10,7 @@ from .decorators import Command, Daemon, Default, RegexCommand
|
||||||
|
|
||||||
# Typing imports
|
# Typing imports
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# Built-in imports
|
# Built-in imports
|
||||||
from typing import List, Any
|
from typing import List, Any
|
||||||
|
@ -18,7 +19,7 @@ if TYPE_CHECKING:
|
||||||
class ModuleMeta:
|
class ModuleMeta:
|
||||||
def _filter_attrs(self, condition: callable[[Any], bool]) -> List[Any]:
|
def _filter_attrs(self, condition: callable[[Any], bool]) -> List[Any]:
|
||||||
# This prevents an infinite loop of getting the attribute
|
# This prevents an infinite loop of getting the attribute
|
||||||
illegal_names = ['commands', 'daemons', 'default']
|
illegal_names = ["commands", "daemons", "default"]
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
|
|
||||||
|
@ -36,8 +37,10 @@ class ModuleMeta:
|
||||||
# The sort puts all the RegexCommand objects at the back, making them
|
# The sort puts all the RegexCommand objects at the back, making them
|
||||||
# be matched last
|
# be matched last
|
||||||
|
|
||||||
return sorted(self._filter_attrs(lambda val: isinstance(val, Command)),
|
return sorted(
|
||||||
key=lambda x: isinstance(x, RegexCommand))
|
self._filter_attrs(lambda val: isinstance(val, Command)),
|
||||||
|
key=lambda x: isinstance(x, RegexCommand),
|
||||||
|
)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def daemons(self) -> List[Daemon]:
|
def daemons(self) -> List[Daemon]:
|
||||||
|
@ -45,5 +48,6 @@ class ModuleMeta:
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def default(self) -> Default:
|
def default(self) -> Default:
|
||||||
return next(iter(self._filter_attrs(
|
return next(
|
||||||
lambda val: isinstance(val, Default))), None)
|
iter(self._filter_attrs(lambda val: isinstance(val, Default))), None
|
||||||
|
)
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .decorators import RegexCommand
|
||||||
|
|
||||||
# Typing imports
|
# Typing imports
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# Built-in imports
|
# Built-in imports
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
@ -33,13 +34,13 @@ class Module(ModuleMeta):
|
||||||
Prefix to activate this module.
|
Prefix to activate this module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
NAME = ''
|
NAME = ""
|
||||||
"""
|
"""
|
||||||
The name is used in various places, such as the config file and the
|
The name is used in various places, such as the config file and the
|
||||||
help function.
|
help function.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
HELP = ''
|
HELP = ""
|
||||||
"""
|
"""
|
||||||
Short description of the module to use in the help function.
|
Short description of the module to use in the help function.
|
||||||
"""
|
"""
|
||||||
|
@ -84,8 +85,9 @@ class Module(ModuleMeta):
|
||||||
for task in self._tasks:
|
for task in self._tasks:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
|
|
||||||
async def __call__(self, cmd: List[str], author: User,
|
async def __call__(
|
||||||
channel: Messageable, mid: int):
|
self, cmd: List[str], author: User, channel: Messageable, mid: int
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Execute the command, if found.
|
Execute the command, if found.
|
||||||
|
|
||||||
|
@ -97,21 +99,26 @@ class Module(ModuleMeta):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if cmd:
|
if cmd:
|
||||||
func = next((func for func in self.commands
|
func = next(
|
||||||
if func.match(cmd[0])), None)
|
(func for func in self.commands if func.match(cmd[0])), None
|
||||||
|
)
|
||||||
|
|
||||||
# Throw error if no function is found
|
# Throw error if no function is found
|
||||||
if not func:
|
if not func:
|
||||||
raise InvalidCommand(f'Unknown command: {cmd}')
|
raise InvalidCommand(f"Unknown command: {cmd}")
|
||||||
|
|
||||||
# A RegexCommand can use the prefix, as it's not a fixed string
|
# A RegexCommand can use the prefix, as it's not a fixed string
|
||||||
if isinstance(func, RegexCommand):
|
if isinstance(func, RegexCommand):
|
||||||
await func(prefix=cmd[0], cmd=cmd[1:], author=author,
|
await func(
|
||||||
channel=channel, mid=mid)
|
prefix=cmd[0],
|
||||||
|
cmd=cmd[1:],
|
||||||
|
author=author,
|
||||||
|
channel=channel,
|
||||||
|
mid=mid,
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
await func(cmd=cmd[1:], author=author, channel=channel,
|
await func(cmd=cmd[1:], author=author, channel=channel, mid=mid)
|
||||||
mid=mid)
|
|
||||||
|
|
||||||
elif self.default:
|
elif self.default:
|
||||||
await self.default(author=author, channel=channel, mid=mid)
|
await self.default(author=author, channel=channel, mid=mid)
|
||||||
|
|
|
@ -2,5 +2,5 @@ from .help import HelpMod
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'HelpMod',
|
"HelpMod",
|
||||||
]
|
]
|
||||||
|
|
|
@ -10,6 +10,7 @@ from .. import Module, default, regex_command
|
||||||
|
|
||||||
# Typing imports
|
# Typing imports
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# Built-in imports
|
# Built-in imports
|
||||||
from typing import List
|
from typing import List
|
||||||
|
@ -24,11 +25,11 @@ class HelpMod(Module):
|
||||||
other modules.
|
other modules.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PREFIX = 'help'
|
PREFIX = "help"
|
||||||
NAME = 'help'
|
NAME = "help"
|
||||||
HELP = 'Shows help info about all modules'
|
HELP = "Shows help info about all modules"
|
||||||
|
|
||||||
@default(help_str='Show help about all modules.')
|
@default(help_str="Show help about all modules.")
|
||||||
async def send_all(self, author: User, channel: Messageable, mid: int):
|
async def send_all(self, author: User, channel: Messageable, mid: int):
|
||||||
embed = Embed()
|
embed = Embed()
|
||||||
|
|
||||||
|
@ -37,20 +38,33 @@ class HelpMod(Module):
|
||||||
|
|
||||||
await channel.send(embed=embed)
|
await channel.send(embed=embed)
|
||||||
|
|
||||||
@regex_command(pattern='.+', help_str='Show help about a certain module.')
|
@regex_command(pattern=".+", help_str="Show help about a certain module.")
|
||||||
async def show_module_help(self, prefix: str, cmd: List[str], author: User,
|
async def show_module_help(
|
||||||
channel: Messageable, mid: int):
|
self,
|
||||||
|
prefix: str,
|
||||||
|
cmd: List[str],
|
||||||
|
author: User,
|
||||||
|
channel: Messageable,
|
||||||
|
mid: int,
|
||||||
|
):
|
||||||
# Yes, this command just ignores cmd at the moment
|
# Yes, this command just ignores cmd at the moment
|
||||||
mod_name = prefix.lower()
|
mod_name = prefix.lower()
|
||||||
mod = next((mod for mod in self._client._modules
|
mod = next(
|
||||||
if mod.NAME.lower() == mod_name), None)
|
(
|
||||||
|
mod
|
||||||
|
for mod in self._client._modules
|
||||||
|
if mod.NAME.lower() == mod_name
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
if mod:
|
if mod:
|
||||||
embed = Embed()
|
embed = Embed()
|
||||||
|
|
||||||
if mod.default:
|
if mod.default:
|
||||||
embed.add_field(name='default', value=mod.default.help_str,
|
embed.add_field(
|
||||||
inline=False)
|
name="default", value=mod.default.help_str, inline=False
|
||||||
|
)
|
||||||
|
|
||||||
for cmd in mod._COMMANDS:
|
for cmd in mod._COMMANDS:
|
||||||
embed.add_field(name=cmd.cmd, value=mod.help_str, inline=False)
|
embed.add_field(name=cmd.cmd, value=mod.help_str, inline=False)
|
||||||
|
|
|
@ -13,3 +13,4 @@ pytest-asyncio>=0.14.0,<1.0.0
|
||||||
twine>=3.2.0,<4.0.0
|
twine>=3.2.0,<4.0.0
|
||||||
Sphinx==3.2.1
|
Sphinx==3.2.1
|
||||||
sphinx-rtd-theme==0.5.0
|
sphinx-rtd-theme==0.5.0
|
||||||
|
black
|
||||||
|
|
Reference in New Issue