Execução dinâmica de comando, análise e armazenamento.
DynCommands permite importar e executar dinamicamente as funções Python. Útil para adicionar comandos a aplicativos IRC Chatbots ou CLI sem reiniciar.
Ao analisar uma string, ela separa o nome do comando dos argumentos e executa a função armazenada com esses argumentos. Cada vez que o analisador é chamado, você pode passar em seus próprios kwargs personalizados aos quais o comando terá acesso.
Todos os módulos de comando são compilados através do RESTRIITETYTHON antes de serem autorizados a ser executados. Você pode desativar a execução restrita definindo CommandParser._unrestricted para true , embora isso seja altamente desencorajado ao executar o código não confiável.
from pathlib import Path
from dyncommands import CommandParser , CommandContext , CommandSource
output : str = ''
def callback ( text , * args ):
global output
output = text
path = Path ( 'path/to/directory' ) # Must be a directory with a `commands.json` file in it
parser = CommandParser ( path ) # Create the parser, which initializes using data located in the path directory
source = CommandSource ( callback ) # Create a source, which is used to talk back to the caller
input_ = 'command-that-returns-wow arg1 arg2' # this command would call zzz__command-that-returns-wow.py with arg1 and arg2
parser . parse ( CommandContext ( input_ , source )) # Parse the new context and run the command and callback (If no errors occur)
assert output == 'wow' Os metadados dos comandos são armazenados no arquivo commands.json do diretório CommandParser.commands_path . É aqui que todos os dados do analisador são carregados e armazenados.
Todos commands.json
| chave | tipo | descrição | padrão | obrigatório |
|---|---|---|---|---|
commandPrefix | corda | As strings devem começar com este prefixo, caso contrário, ela é ignorada. String vazia aceita tudo. | N / D | Sim |
commands | Array [ Comando ] | Contém metadados para os módulos de comando armazenados. | N / D | Sim |
| chave | tipo | descrição | padrão | obrigatório |
|---|---|---|---|---|
name | corda | Identifica exclusivamente o comando para o comandoparser. | N / D | Sim |
usage | corda | Informações de uso (como usar o ARGS). | "" | Não |
description | corda | Descrição do comando. | "" | Não |
permission | Inteiro | O nível de permissão que o ComandSource exige executar o comando. | 0 | Não |
function | booleano , nulo | Se existe um módulo Python associado a ser carregado. | nulo | Não |
children | Array [ Comando ] | Subcomando; Estes são tratados pela função dos pais. (Nenhum módulo associado para si). | [] | Não |
overridable | booleano | Se o CommandParser pode substituir qualquer dados dentro desse objeto (deve ser ativado manualmente). | verdadeiro | Não |
disabled | booleano | Se True Still Carregar o comando, mas levante um DisabledError ao tentar executar. | falso | Não |
NOTA: Os módulos de comandos não são carregados, a menos que estejam listados em commands.json com a tecla function definida como true .
commands.json Conteúdo: {
"commandPrefix" : " ! " ,
"commands" : [
{
"name" : " test " ,
"usage" : " test [*args:any] " ,
"description" : " Test command. " ,
"permission" : 500 ,
"function" : true
},
{
"name" : " test2 " ,
"function" : false
}
]
} Os comandos carregados dinamicamente são indicados pelo nome do arquivo com um prefixo de "ZZZ__". Dentro de um módulo de comando, há uma função definida como command . Esta função será mapeada para o atributo de função de um Command e armazenada na memória para execução. A função tem acesso a quaisquer args que foram analisados, assim como os kwargs:
' self ' ( Command ), que abriga os metadados para o comando que está sendo executado.
' Parsers ' ( CommandParser ), que armazena a lista de comandos registrados e dados de comando.
' Context ' ( CommandContext ), que fornece o CommandSource e o texto original enviado para análise.
CommandParser.parse(context: CommandContext, **kwargs) . Como os comandos não podem importar seus próprios módulos, alguns estão incluídos em globais ( math , random e string ). Outros atributos incluídos no escopo global são: getitem ( operator.getItem ) e ImproperUsageError ( dynCommands.exceptions.improperusageError ).
def command ( * args , ** kwargs ):
self , context = kwargs . pop ( 'self' ), kwargs . pop ( 'context' )
source = context . source
if len ( args ) == 2 :
amount , sides = abs ( int ( getitem ( args , 0 ))), abs ( int ( getitem ( args , 1 )))
if amount > 0 and sides > 0 :
dice_rolls = [ f" { ( str ( i + 1 ) + ':' ) if amount > 1 else '' } { str ( random . randint ( 1 , sides )) } / { sides } " for i in range ( amount )]
source . send_feedback ( f"/me U0001f3b2 { source . display_name } rolled { 'a die' if amount == 1 else str ( amount ) + ' dice' } with { sides } side { '' if sides == 1 else 's' } : { ', ' . join ( dice_rolls ) } U0001f3b2 " )
else :
raise ImproperUsageError ( self , context )
else :
raise ImproperUsageError ( self , context ) A qualquer momento, você pode chamar CommandParser.reload() para recarregar todos os módulos de comando e metadados do armazenamento em disco.
../
│
├───[commands_path]/
│ ├─── commands.json
│ ├─── zzz__[command1].py
│ ├─── zzz__[command2].py
│ └─── zzz__[command3].py
│
Para adicionar comandos, você pode inserir manualmente os dados em um arquivo commands.json ou usar o método CommandParser.add_command(text: str, link: bool = False, **kwargs) . A maneira mais fácil de usar esse método é ler o módulo de comando como texto e passá -lo para o primeiro argumento. Você também pode armazenar módulos de comando on -line para permitir a instalação remota, como definir o parâmetro de link como True , lerá o texto como um link e obterá os dados de texto bruto desse link. Ex: GIST e PASTEBIN.
Nota: Ao adicionar um comando, os metadados para 'nome' devem ser preenchidos. Isso pode ser feito na forma de comentários.
Remover um comando já adicionado é relativamente fácil. Basta ligar para CommandParser.remove_command(name: str) com o nome do comando que você deseja remover, e ele excluirá os metadados e o módulo de comando do disco.
Se você não deseja excluir o comando ao remover, uma alternativa melhor é desativá -lo com CommandParser.set_disabled(name: str, value: bool) .
# Name: points
# Usage: points [get (username:string) | set (username:string amount:integer)]
# Description: Get your current points
# Permission: 0
# Children: [{'name': 'get', 'usage': 'get (username:string)', 'permission':0}, {'name': 'set', 'usage': 'set (username:string amount:integer)', 'permission':500}]
def command ( * args , ** kwargs ):
... parser = CommandParser ( './' )
with open ( 'some_metadata.json' ) as _file :
get_ = { 'name' : 'get' , 'usage' : 'get (username:string)' , 'permission' : 0 }
set_ = { 'name' : 'set' , 'usage' : 'set (username:string amount:integer)' , 'permission' : 500 }
children = [ get_ , set_ ]
parser . add_command ( _file . read (), name = 'my-command' , description = 'Command with child commands.' , children = children ) parser = CommandParser ( './' )
with open ( 'some_metadata.json' ) as _file :
metadata = json . load ( _file )
parser . add_command ( 'https://gist.github.com/random/892hdh2fh389x0wcmksio7m' , link = True , ** metadata ) O DynCommand CommandParser suporta nativamente o manuseio do nível de permissão, para que você não precise implementar um sistema semelhante em todas as funções de comando.
Cada comando possui a permission de valor de metadados ((com exceção do valor especial -1 ) é o nível mínimo de permissão exigido no CommandSource . -1 representa um requisito "infinito", onde nenhum CommandSource poderá executá -lo enquanto o sistema de permissão estiver ativo.
Para desativar o sistema de permissão, defina o atributo CommandParser _ignore_permission como true. Nota: Como esse atributo começa com um "_", a tentativa de alterá -lo dentro da função de um comando resultará em compilação com falha e uma exceção.