move arg stuff to its own file

This commit is contained in:
Chris W 2023-10-13 12:34:08 -06:00
parent ec00796cc0
commit b989aaeea2
10 changed files with 140 additions and 142 deletions

View File

@ -1,12 +1,10 @@
from typing import Dict, List, Union
import logging import logging
from telethon import TelegramClient from telethon import TelegramClient
from telethon.tl.custom.message import Message from telethon.tl.custom.message import Message
from cyber_fenneko.internal.constants import GLOBAL_ARGS from cyber_fenneko.internal.constants import GLOBAL_ARGS
from cyber_fenneko.internal.command import Command from cyber_fenneko.internal.command import Command
from cyber_fenneko.internal.command_context import CommandContext 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: class Bot:
def __init__(self, client: TelegramClient): 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 the command is marked as 'delete', delete the message that triggered it
if ctx.args['delete']: if ctx.args['delete']:
await event.message.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

View File

@ -6,7 +6,7 @@ from collections import OrderedDict
from telethon.tl.types import TypeMessageEntity, MessageEntityPre from telethon.tl.types import TypeMessageEntity, MessageEntityPre
from .. import bot from .. import bot
from ..internal.command_context import CommandContext 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' ENDPOINT_URL = 'https://inkify.0x45.st'

View File

@ -4,7 +4,7 @@ from telethon.tl.types import MessageEntityPre
from telethon.tl.custom.message import Message from telethon.tl.custom.message import Message
from .. import bot from .. import bot
from ..internal.command_context import CommandContext 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' ENDPOINT = 'https://0x45.st/api/pastes'

View File

@ -1,7 +1,7 @@
import time import time
from .. import bot from .. import bot
from ..internal.command_context import CommandContext from ..internal.command_context import CommandContext
from ..internal.arg import Arg, ArgType from ..internal.arg_parser import Arg, ArgType
@bot.command( @bot.command(
'ping', 'ping',

View File

@ -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

View File

@ -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

View File

@ -1,7 +1,7 @@
import itertools import itertools
from typing import List from typing import List
from .arg import Arg from .arg_parser import Arg
from ..bot import GLOBAL_ARGS from ..bot import GLOBAL_ARGS
class Command: class Command:

View File

@ -4,7 +4,7 @@ from telethon.events import NewMessage
from telethon.tl.custom.message import Message from telethon.tl.custom.message import Message
from .. import bot from .. import bot
from .arg import Arg from .arg_parser import Arg
class CommandContext: class CommandContext:

View File

@ -1,4 +1,4 @@
from .arg import Arg, ArgType from .arg_parser import Arg, ArgType
# Args that exist on every command # Args that exist on every command
GLOBAL_ARGS = [ GLOBAL_ARGS = [

View File