Ejecución de comandos dinámicos, análisis y almacenamiento.
DynCommands le permite importar dinámicamente y ejecutar funciones de Python. Útil para agregar comandos a las aplicaciones IRC Chatbots o CLI sin reiniciar.
Al analizar una cadena, separa el nombre del comando de los argumentos y ejecuta la función almacenada con esos argumentos. Cada vez que se llama al analizador, puede pasar en sus propios kwargs personalizados a los que el comando tendrá acceso.
Todos los módulos de comando se compilan a través de RestrictEdpython antes de que se les permita ejecutar. Puede desactivar la ejecución restringida configurando CommandParser._unrestricted a True , aunque esto se desanimó altamente cuando se ejecuta un código no confiable.
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' Los metadatos para los comandos se almacenan en el archivo commands.json del directorio de CommandParser.commands_path . Aquí es donde se cargan y almacenan todos los datos para el analizador.
Todos los archivos commands.json se validan con los esquemas JSON a través del paquete Jsonschema Python
| llave | tipo | descripción | por defecto | requerido |
|---|---|---|---|---|
commandPrefix | cadena | Las cadenas deben comenzar con este prefijo, de lo contrario se ignora. La cadena vacía acepta todo. | N / A | Sí |
commands | matriz [ comando ] | Contiene metadatos para los módulos de comando almacenados. | N / A | Sí |
| llave | tipo | descripción | por defecto | requerido |
|---|---|---|---|---|
name | cadena | Identifica de manera única el comando al CommandParser. | N / A | Sí |
usage | cadena | Información de uso (cómo usar Args). | " | No |
description | cadena | Descripción del comando. | " | No |
permission | entero | El nivel de permiso que el comandante requiere para ejecutar el comando. | 0 | No |
function | booleano , nulo | Si hay un módulo de pitón asociado para cargar. | nulo | No |
children | matriz [ comando ] | Submands; Estos son manejados por la función de los padres. (No hay módulos asociados para ellos mismos). | [] | No |
overridable | booleano | Si el CommandParser puede anular cualquier dato dentro de este objeto (debe estar habilitado manualmente). | verdadero | No |
disabled | booleano | Si el comando verdadero todavía carga, pero levante un desactivado cuando intente ejecutar. | FALSO | No |
NOTA: Los módulos de comandos no se cargan a menos que se enumeren en commands.json con la tecla function establecida en True .
commands.json contenido: {
"commandPrefix" : " ! " ,
"commands" : [
{
"name" : " test " ,
"usage" : " test [*args:any] " ,
"description" : " Test command. " ,
"permission" : 500 ,
"function" : true
},
{
"name" : " test2 " ,
"function" : false
}
]
} Los comandos cargados dinámicamente se denotan por nombre de archivo con un prefijo de "Zzz__". Dentro de un módulo de comando, hay una función definida como command . Esta función se asignará al atributo de función de un Command y se almacenará en la memoria para su ejecución. La función tiene acceso a cualquier args que se analizara, así como a Kwargs:
' Self ' ( Command ), que alberga los metadatos para el comando que se está ejecutando.
' Parser ' ( CommandParser ), que almacena la lista de comandos registrados y datos de comandos.
' context ' ( CommandContext ), que suministra el CommandSource y el texto original enviado para análisis.
CommandParser.parse(context: CommandContext, **kwargs) . Dado que los comandos no pueden importar sus propios módulos, algunos están incluidos en Globals ( math , random y string ). Otros atributos incluidos en el alcance global son: getitem ( operador.getItem ) e ImproperUsageError ( dynCommands.Exceptions.impropropeUsageError ).
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 ) En cualquier momento, puede llamar a CommandParser.reload() para recargar todos los módulos de comando y metadatos desde el almacenamiento en disco.
../
│
├───[commands_path]/
│ ├─── commands.json
│ ├─── zzz__[command1].py
│ ├─── zzz__[command2].py
│ └─── zzz__[command3].py
│
Para agregar comandos, puede ingresar manualmente los datos en un archivo de commands.json o usar el método CommandParser.add_command(text: str, link: bool = False, **kwargs) . La forma más fácil de usar este método es leer el módulo de comando como texto y pasarlo al primer argumento. También puede almacenar módulos de comando en línea para permitir la instalación remota, ya que configurar el parámetro del enlace en verdadero leerá el texto como un enlace y obtendrá los datos de texto sin procesar de ese enlace. Ej: GIST y Pastebin.
Nota: Al agregar un comando, los metadatos para 'nombre' deben llenarse. Esto se puede hacer en forma de comentarios.
Eliminar un comando ya agregado es relativamente fácil. Simplemente llame CommandParser.remove_command(name: str) con el nombre del comando que desea eliminar, y eliminará tanto los metadatos como el módulo de comando del disco.
Si no desea eliminar el comando al eliminar, una mejor alternativa es deshabilitarlo con 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 ) DynCommand CommandParser es compatible con el manejo de nivel de permiso, por lo que no tiene que implementar un sistema similar en cada función de comando.
Cada comando tiene el permission de valor de metadatos (con la excepción del valor especial -1 ) es el nivel de permiso mínimo requerido de los CommandSource . -1 representa un requisito "infinito", donde ningún CommandSource podrá ejecutarlo mientras el sistema de permiso esté activo.
Para deshabilitar el sistema de permiso, establezca el atributo de CommandParser s _ignore_permission a verdadero. Nota: Dado que este atributo comienza con un "_", intentar cambiarlo desde el interior de la función de un comando dará como resultado una compilación fallida y una excepción.