move arg stuff to its own file
This commit is contained in:
parent
ec00796cc0
commit
b989aaeea2
|
@ -1,12 +1,10 @@
|
|||
from typing import Dict, List, Union
|
||||
|
||||
import logging
|
||||
from telethon import TelegramClient
|
||||
from telethon.tl.custom.message import Message
|
||||
from cyber_fenneko.internal.constants import GLOBAL_ARGS
|
||||
from cyber_fenneko.internal.command import Command
|
||||
from cyber_fenneko.internal.command_context import CommandContext
|
||||
from cyber_fenneko.internal.arg import Arg, ArgType
|
||||
from cyber_fenneko.internal.arg_parser import parse_args
|
||||
|
||||
class Bot:
|
||||
def __init__(self, client: TelegramClient):
|
||||
|
@ -90,100 +88,3 @@ class Bot:
|
|||
# if the command is marked as 'delete', delete the message that triggered it
|
||||
if ctx.args['delete']:
|
||||
await event.message.delete()
|
||||
|
||||
def parse_args(self, text: str, cmd_args: List[Arg]) -> (Dict[str, Arg], str):
|
||||
"""
|
||||
Take an incoming string and parse args from it.
|
||||
Args are defined one of three ways:
|
||||
- name:value
|
||||
- .name - shorthand for name:true
|
||||
- !name - shorthand for name:false
|
||||
Args are also only valid at the beginning of a message,
|
||||
so as soon as we encounter a non-arg we stop parsing.
|
||||
"""
|
||||
args: Dict[str, Arg] = { arg.name: arg for arg in cmd_args }
|
||||
aliases: Dict[str, str] = { alias: arg.name for arg in cmd_args for alias in arg.aliases }
|
||||
|
||||
get_arg = lambda name: args.get(name) or args.get(aliases.get(name))
|
||||
|
||||
parsed_args: Dict[str, Union[str, int, bool]] = {}
|
||||
tokens = text.split()
|
||||
while tokens:
|
||||
token = tokens.pop(0)
|
||||
|
||||
if token.startswith('.'):
|
||||
# shorthand for name:true
|
||||
arg = get_arg(token[1:])
|
||||
if arg:
|
||||
if args[arg.name].type == ArgType.bool:
|
||||
parsed_args[arg.name] = True
|
||||
else:
|
||||
raise ValueError(f'Arg "{arg.name}" is not a boolean, but a boolean was provided')
|
||||
else:
|
||||
self.logger.warning(f'Unknown arg "{arg.name}"')
|
||||
continue
|
||||
elif token.startswith('!'):
|
||||
# shorthand for name:false
|
||||
arg = get_arg(token[1:])
|
||||
if arg:
|
||||
if arg.type == ArgType.bool:
|
||||
parsed_args[arg.name] = False
|
||||
else:
|
||||
raise ValueError(f'Arg "{arg.name}" is not a boolean, but a boolean was provided')
|
||||
else:
|
||||
self.logger.warning(f'Unknown arg "{arg.name}"')
|
||||
continue
|
||||
elif ':' in token:
|
||||
# name:value
|
||||
# value might be a quoted string, so we need to handle that
|
||||
arg_name, arg_value = token.split(':', 1)
|
||||
arg = get_arg(arg_name)
|
||||
if arg:
|
||||
if arg.type == ArgType.str:
|
||||
# we need to check if the value starts with a quote,
|
||||
# if so we need to loop through tokens until we find the end quote
|
||||
if arg_value.startswith('"'):
|
||||
while not arg_value.endswith('"') and tokens:
|
||||
arg_value += ' ' + tokens.pop(0)
|
||||
if not arg_value.endswith('"'):
|
||||
raise ValueError(f'Unterminated string for arg "{arg.name}"')
|
||||
arg_value = arg_value[1:-1]
|
||||
parsed_args[arg.name] = arg_value
|
||||
elif arg.type == ArgType.bool:
|
||||
# if the arg is a boolean, we need to check if the value is true or false
|
||||
if arg_value.lower() == 'true':
|
||||
parsed_args[arg.name] = True
|
||||
elif arg_value.lower() == 'false':
|
||||
parsed_args[arg.name] = False
|
||||
else:
|
||||
raise ValueError(f'Invalid boolean value {arg_value} for arg "{arg.name}"')
|
||||
elif arg.type == ArgType.int:
|
||||
# if the arg is an int, we need to check if the value is an int
|
||||
try:
|
||||
parsed_args[arg.name] = int(arg_value)
|
||||
except ValueError:
|
||||
raise ValueError(f'Invalid int value {arg_value} for arg "{arg.name}"')
|
||||
else:
|
||||
raise ValueError(f'Invalid arg type {arg.type} for arg "{arg.name}"')
|
||||
else:
|
||||
self.logger.warning(f'Unknown arg "{arg.name}"')
|
||||
continue
|
||||
|
||||
# If we get here, we've encountered a non-arg token
|
||||
# so we stop parsing
|
||||
break
|
||||
|
||||
# Check if any required args are missing
|
||||
missing_args = [arg.name for arg in cmd_args if arg.required and arg.name not in parsed_args]
|
||||
if missing_args:
|
||||
raise ValueError(f'Missing required args: {", ".join(missing_args)}')
|
||||
|
||||
# remove the parsed args from the text
|
||||
text = ' '.join(tokens[len(parsed_args):])
|
||||
|
||||
# add the default values for any args that weren't specified
|
||||
for arg in args.values():
|
||||
if arg.name not in parsed_args:
|
||||
parsed_args[arg.name] = arg.default
|
||||
|
||||
return parsed_args, text
|
|
@ -6,7 +6,7 @@ from collections import OrderedDict
|
|||
from telethon.tl.types import TypeMessageEntity, MessageEntityPre
|
||||
from .. import bot
|
||||
from ..internal.command_context import CommandContext
|
||||
from ..internal.arg import Arg, ArgType
|
||||
from ..internal.arg_parser import Arg, ArgType
|
||||
|
||||
ENDPOINT_URL = 'https://inkify.0x45.st'
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ from telethon.tl.types import MessageEntityPre
|
|||
from telethon.tl.custom.message import Message
|
||||
from .. import bot
|
||||
from ..internal.command_context import CommandContext
|
||||
from ..internal.arg import Arg, ArgType
|
||||
from ..internal.arg_parser import Arg, ArgType
|
||||
|
||||
ENDPOINT = 'https://0x45.st/api/pastes'
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import time
|
||||
from .. import bot
|
||||
from ..internal.command_context import CommandContext
|
||||
from ..internal.arg import Arg, ArgType
|
||||
from ..internal.arg_parser import Arg, ArgType
|
||||
|
||||
@bot.command(
|
||||
'ping',
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
|
||||
ArgType = Enum('ArgType', ['str', 'int', 'bool', 'peer_id'])
|
||||
|
||||
class Arg:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
*,
|
||||
# The type of this argument
|
||||
type: ArgType = ArgType.str,
|
||||
|
||||
# Aliases for this argument
|
||||
aliases: List[str] = [],
|
||||
|
||||
# Is this argument required?
|
||||
required: bool = False,
|
||||
|
||||
# The default value for this argument
|
||||
default: any = None,
|
||||
|
||||
# The description for this argument, displayed in the help command
|
||||
description: str = None,
|
||||
|
||||
# Special designation for global args
|
||||
global_arg: bool = False,
|
||||
):
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.aliases = aliases
|
||||
self.required = required
|
||||
self.default = default
|
||||
self.description = description
|
||||
self.global_arg = global_arg
|
|
@ -0,0 +1,133 @@
|
|||
from enum import Enum
|
||||
from typing import Dict, List, Union
|
||||
|
||||
|
||||
ArgType = Enum('ArgType', ['str', 'int', 'bool', 'peer_id'])
|
||||
|
||||
class Arg:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
*,
|
||||
# The type of this argument
|
||||
type: ArgType = ArgType.str,
|
||||
|
||||
# Aliases for this argument
|
||||
aliases: List[str] = [],
|
||||
|
||||
# Is this argument required?
|
||||
required: bool = False,
|
||||
|
||||
# The default value for this argument
|
||||
default: any = None,
|
||||
|
||||
# The description for this argument, displayed in the help command
|
||||
description: str = None,
|
||||
|
||||
# Special designation for global args
|
||||
global_arg: bool = False,
|
||||
):
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.aliases = aliases
|
||||
self.required = required
|
||||
self.default = default
|
||||
self.description = description
|
||||
self.global_arg = global_arg
|
||||
|
||||
def parse_args(self, text: str, cmd_args: List[Arg]) -> (Dict[str, Arg], str):
|
||||
"""
|
||||
Take an incoming string and parse args from it.
|
||||
Args are defined one of three ways:
|
||||
- name:value
|
||||
- .name - shorthand for name:true
|
||||
- !name - shorthand for name:false
|
||||
Args are also only valid at the beginning of a message,
|
||||
so as soon as we encounter a non-arg we stop parsing.
|
||||
"""
|
||||
args: Dict[str, Arg] = { arg.name: arg for arg in cmd_args }
|
||||
aliases: Dict[str, str] = { alias: arg.name for arg in cmd_args for alias in arg.aliases }
|
||||
|
||||
get_arg = lambda name: args.get(name) or args.get(aliases.get(name))
|
||||
|
||||
parsed_args: Dict[str, Union[str, int, bool]] = {}
|
||||
tokens = text.split()
|
||||
while tokens:
|
||||
token = tokens.pop(0)
|
||||
|
||||
if token.startswith('.'):
|
||||
# shorthand for name:true
|
||||
arg = get_arg(token[1:])
|
||||
if arg:
|
||||
if args[arg.name].type == ArgType.bool:
|
||||
parsed_args[arg.name] = True
|
||||
else:
|
||||
raise ValueError(f'Arg "{arg.name}" is not a boolean, but a boolean was provided')
|
||||
else:
|
||||
self.logger.warning(f'Unknown arg "{arg.name}"')
|
||||
continue
|
||||
elif token.startswith('!'):
|
||||
# shorthand for name:false
|
||||
arg = get_arg(token[1:])
|
||||
if arg:
|
||||
if arg.type == ArgType.bool:
|
||||
parsed_args[arg.name] = False
|
||||
else:
|
||||
raise ValueError(f'Arg "{arg.name}" is not a boolean, but a boolean was provided')
|
||||
else:
|
||||
self.logger.warning(f'Unknown arg "{arg.name}"')
|
||||
continue
|
||||
elif ':' in token:
|
||||
# name:value
|
||||
# value might be a quoted string, so we need to handle that
|
||||
arg_name, arg_value = token.split(':', 1)
|
||||
arg = get_arg(arg_name)
|
||||
if arg:
|
||||
if arg.type == ArgType.str:
|
||||
# we need to check if the value starts with a quote,
|
||||
# if so we need to loop through tokens until we find the end quote
|
||||
if arg_value.startswith('"'):
|
||||
while not arg_value.endswith('"') and tokens:
|
||||
arg_value += ' ' + tokens.pop(0)
|
||||
if not arg_value.endswith('"'):
|
||||
raise ValueError(f'Unterminated string for arg "{arg.name}"')
|
||||
arg_value = arg_value[1:-1]
|
||||
parsed_args[arg.name] = arg_value
|
||||
elif arg.type == ArgType.bool:
|
||||
# if the arg is a boolean, we need to check if the value is true or false
|
||||
if arg_value.lower() == 'true':
|
||||
parsed_args[arg.name] = True
|
||||
elif arg_value.lower() == 'false':
|
||||
parsed_args[arg.name] = False
|
||||
else:
|
||||
raise ValueError(f'Invalid boolean value {arg_value} for arg "{arg.name}"')
|
||||
elif arg.type == ArgType.int:
|
||||
# if the arg is an int, we need to check if the value is an int
|
||||
try:
|
||||
parsed_args[arg.name] = int(arg_value)
|
||||
except ValueError:
|
||||
raise ValueError(f'Invalid int value {arg_value} for arg "{arg.name}"')
|
||||
else:
|
||||
raise ValueError(f'Invalid arg type {arg.type} for arg "{arg.name}"')
|
||||
else:
|
||||
self.logger.warning(f'Unknown arg "{arg.name}"')
|
||||
continue
|
||||
|
||||
# If we get here, we've encountered a non-arg token
|
||||
# so we stop parsing
|
||||
break
|
||||
|
||||
# Check if any required args are missing
|
||||
missing_args = [arg.name for arg in cmd_args if arg.required and arg.name not in parsed_args]
|
||||
if missing_args:
|
||||
raise ValueError(f'Missing required args: {", ".join(missing_args)}')
|
||||
|
||||
# remove the parsed args from the text
|
||||
text = ' '.join(tokens[len(parsed_args):])
|
||||
|
||||
# add the default values for any args that weren't specified
|
||||
for arg in args.values():
|
||||
if arg.name not in parsed_args:
|
||||
parsed_args[arg.name] = arg.default
|
||||
|
||||
return parsed_args, text
|
|
@ -1,7 +1,7 @@
|
|||
import itertools
|
||||
from typing import List
|
||||
|
||||
from .arg import Arg
|
||||
from .arg_parser import Arg
|
||||
from ..bot import GLOBAL_ARGS
|
||||
|
||||
class Command:
|
||||
|
|
|
@ -4,7 +4,7 @@ from telethon.events import NewMessage
|
|||
from telethon.tl.custom.message import Message
|
||||
|
||||
from .. import bot
|
||||
from .arg import Arg
|
||||
from .arg_parser import Arg
|
||||
|
||||
|
||||
class CommandContext:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from .arg import Arg, ArgType
|
||||
from .arg_parser import Arg, ArgType
|
||||
|
||||
# Args that exist on every command
|
||||
GLOBAL_ARGS = [
|
||||
|
|
Loading…
Reference in New Issue