maiden est un ensemble de systèmes pour vous aider à créer des applications et des bibliothèques qui interagissent avec les serveurs de discussion. Il peut vous aider à créer un chatbot ou un client de chat général. Il propose également une variété de parties qui devraient faciliter grandement l'écriture d'un client pour un nouveau protocole de discussion.
Si vous souhaitez uniquement utiliser maiden pour configurer un robot, les étapes à suivre sont plutôt simples. Nous voudrons d’abord charger maiden et tous les modules et composants que vous souhaitez utiliser dans votre bot.
(ql:quickload '( maiden maiden -irc maiden -commands maiden -silly))
Et puis nous créerons un noyau avec des instances de consommateurs ajoutées comme nous aimerions qu'elles le soient.
(defvar *core* ( maiden :make-core
'(: maiden -irc :nickname " maiden Test" :host "irc.freenode.net" :channels ("##testing"))
: maiden -commands
: maiden -silly))
La commande make-core prend soit les noms de packages (sous forme de chaînes ou de symboles) des consommateurs à ajouter, soit le nom de classe direct d'un consommateur. Dans le premier cas, il essaiera de trouver lui-même le nom de classe de consommateur approprié.
Et c'est tout. make-core
créera un noyau, instanciera tous les consommateurs, les y ajoutera et tout démarrera. Un butin des modules fournis pour maiden utilisera une sorte de configuration ou de stockage persistant. Pour sa gestion, voir le sous-système de stockage.
Afin d'utiliser maiden comme framework, vous devez d'abord définir votre propre système et package comme d'habitude pour un projet. Pour l'instant, nous allons simplement utiliser le package maiden -user
pour jouer. Ensuite, nous voudrons définir un consommateur. Cela peut être fait avec define-consumer
.
(in-package #: maiden -user)
(define-consumer ping-notifier (agent)
())
Habituellement, vous souhaiterez définir un agent. Les agents ne peuvent exister qu'une seule fois sur un noyau. Nous passerons plus tard en revue un exemple pour un client. Désormais, à partir de maintenant, nous pouvons définir nos propres méthodes et fonctions qui se spécialisent ou agissent sur la classe de consommateurs, comme vous en auriez l'habitude dans la programmation CLOS générale. Ensuite, nous définirons notre propre événement que nous utiliserons pour envoyer des « requêtes ping » au système.
(define-event ping (passive-event)
())
L'événement est défini comme un passive-event
car il ne demande pas directement qu'une action soit entreprise, mais informe plutôt le système d'un ping qui se produit. Maintenant, afin de réellement faire interagir le consommateur avec le système d'événements, nous souhaitons également définir des gestionnaires. Cela peut être fait avec define-handler
.
(define-handler (ping-notifier ping-receiver ping) (c ev)
(v:info :ping "Received a ping: ~a" ev))
Ceci définit un gestionnaire appelé ping-receiver
sur notre consommateur ping-notifier
. Il précise également qu'il écoutera les événements de type ping
. L'arglist dit ensuite que l'instance du consommateur est liée à c
et l'instance d'événement à ev
. Le corps enregistre ensuite simplement un message d'information en utilisant Verbose.
Testons cela très rapidement.
(defvar *core* (make-core 'ping-notifier))
(do-issue *core* ping)
Cela devrait imprimer le message d'état sur le REPL comme prévu. Et c’est l’essentiel de l’utilisation de ce système. Notez que pour faire des choses réellement utiles, vous souhaiterez probablement utiliser certains des sous-systèmes préexistants fournis par le maiden projet en dehors du noyau. Ceux-ci vous aideront avec les utilisateurs, les canaux, les comptes, les commandes, la mise en réseau, le stockage, etc. Gardez également à l’esprit que vous pouvez également utiliser les fonctionnalités proposées par Deeds, telles que le filtrage des expressions pour les gestionnaires.
Jetons maintenant un coup d'œil à un type primitif de client. Le client pourra simplement écrire dans un fichier via des événements.
(define-consumer file-client (client)
((file :initarg :file :accessor file))
(:default-initargs :file (error "FILE required.")))
(define-event write-event (client-event active-event)
((sequence :initarg :sequence))
(:default-initargs :sequence (error "SEQUENCE required.")))
Nous avons fait de l' write-event
un client-event
car il doit être spécifique au client sur lequel nous voulons écrire, et nous en avons fait un active-event
puisqu'il demande que quelque chose se produise. Définissons maintenant notre gestionnaire qui se chargera d'écrire la séquence dans un fichier.
(define-handler (file-client writer write-event) (c ev sequence)
:match-consumer 'client
(with-open-file (stream (file c) :direction :output :if-exists :append :if-does-not-exist :create)
(write-sequence sequence stream)))
L'option :match-consumer
modifie le filtre du gestionnaire de telle sorte que le filtre ne transmette que les événements dont l'emplacement client
contient la même instance file-client
à laquelle appartient l'instance actuelle du gestionnaire. Ceci est important, car chaque instance de file-client
recevra ses propres instances de ses gestionnaires sur un noyau. Sans cette option, l' write-event
serait géré par chaque instance du file-client
quelle que soit l'instance à laquelle l'événement était destiné. Notez également que nous avons ajouté un argument sequence
à la liste d'arguments du gestionnaire. Cet argument sera rempli avec le créneau approprié de l'événement. Si aucun emplacement de ce type n'a pu être trouvé, une erreur est signalée.
Il est temps de le tester. Nous allons simplement réutiliser le noyau d'en haut.
(add-to-core *core* '(file-client :file "~/foo" :name :foo)
'(file-client :file "~/bar" :name :bar))
(do-issue *core* write-event :sequence "foo" :client (consumer :foo *core*))
(do-issue *core* write-event :sequence "bar" :client (consumer :bar *core*))
(alexandria:read-file-into-string "~/foo") ; => "foo"
(alexandria:read-file-into-string "~/bar") ; => "bar"
Comme vous pouvez le voir, les événements ont été dirigés vers les instances de gestionnaire appropriées en fonction du client souhaité, et les fichiers contiennent donc ce que nous attendons d'eux.
Enfin, il convient de mentionner qu'il est également possible d'ajouter et de supprimer dynamiquement des gestionnaires au moment de l'exécution, et même de le faire pour des gestionnaires qui ne sont pas associés à un consommateur particulier. Ceci est souvent utile lorsque vous devez attendre un événement de réponse venant de quelque part. Pour gérer la logique de cette opération de manière asynchrone et conserver l'impression d'un flux impératif, maiden propose - tout comme le fait Deeds - une macro with-awaiting
. Il peut être utilisé comme suit :
(with-awaiting (core event-type) (ev some-field)
(do-issue core initiating-event)
:timeout 20
some-field)
with-awaiting
est très similaire à define-handler
, à l'exception du fait qu'il ne prend pas de nom et qu'au lieu d'un nom de consommateur au début, il a besoin d'une instance principale ou de consommateur. Il nécessite également une option supplémentaire qui serait autrement inutilisée, la :timeout
. Un autre supplément requis est le "formulaire de configuration" après la liste d'arguments. Afin de tout gérer correctement et de garantir qu'aucune condition de concurrence critique ne puisse se produire dans le système, vous devez lancer le processus qui déclenchera l'événement de réponse éventuel dans ce formulaire de configuration. Si vous le lancez avant cette date, l'événement de réponse peut être envoyé avant que le gestionnaire temporaire ne soit configuré dans le système et il apparaîtra comme s'il n'était jamais arrivé.
Et c'est à peu près toutes les bases. Comme mentionné ci-dessus, jetez un œil aux sous-systèmes inclus dans ce projet, car ils vous aideront avec toutes sortes de tâches et de problèmes courants liés aux systèmes de discussion, etc.
Avant de comprendre maiden , cela vaut la peine de comprendre les actes, ne serait-ce qu'à un niveau superficiel. maiden s'appuie assez fortement sur cela.
Un core
est la partie centrale d’une maiden configuration. Il est chargé de gérer et d’orchestrer les autres composants du système. Vous pouvez avoir plusieurs cœurs exécutés simultanément dans la même image Lisp et même partager des composants entre eux.
Plus précisément, un Core est composé d'une boucle d'événements et d'un ensemble de consommateurs. La boucle d'événements est chargée de transmettre les événements aux gestionnaires. Les consommateurs sont responsables de l'attachement des gestionnaires à la boucle d'événements. Les opérations que vous souhaiterez probablement effectuer sur un noyau sont les suivantes : lui envoyer des événements par issue
, y ajouter des consommateurs par add-consumer
ou en supprimer un consommateur par remove-consumer
.
Afin de faciliter la création d'un noyau utile auquel sont ajoutés des consommateurs, vous pouvez utiliser les fonctions make-core
et add-to-core
.
Un event
est un objet qui représente un changement dans le système. Les événements peuvent être utilisés soit pour représenter un changement survenu, soit pour représenter une demande de changement. Ceux-ci sont appelés respectivement passive-event
et active-event
.
En général, vous utiliserez les événements des manières suivantes :
Un consumer
est une classe qui représente un composant du système. Chaque consommateur peut être associé à une multitude de gestionnaires, qui réagiront aux événements du système. Les consommateurs se répartissent en deux supertypes de base, agent
et client
. Les agents sont des consommateurs qui ne devraient exister qu'une seule fois sur un noyau, car ils implémentent des fonctionnalités qui n'auraient pas de sens d'être multiplexées d'une manière ou d'une autre. Les clients, quant à eux, représentent une sorte de pont vers un système extérieur et devraient naturellement être autorisés à avoir plusieurs instances sur le même cœur.
Ainsi, développer un ensemble de commandes ou une interface quelconque mènerait probablement à un agent, alors que s'interfacer avec un service comme XMPP mènerait à un client.
La définition d'un consommateur doit se faire avec define-consumer
, qui est similaire au standard defclass
, mais garantit que les superclasses et métaclasses sont correctement configurées.
handler
sont des objets qui détiennent une fonction qui effectue certaines actions lorsqu'un événement particulier est émis sur le noyau. Chaque gestionnaire est lié à un consommateur particulier et est supprimé ou ajouté à la boucle d'événements du noyau lorsque le consommateur est supprimé ou ajouté au noyau.
La définition du gestionnaire s'effectue via l'un des éléments define-handler
, define-function-handler
, define-instruction
ou define-query
. Chacun s'appuyant successivement sur le dernier pour fournir un raccourci plus large pour les exigences communes. Notez que la manière dont un gestionnaire reçoit réellement ses événements peut différer. Jetez un œil à la documentation de Deeds pour voir quelles classes de gestionnaires sont disponibles.
Le maiden projet comprend quelques sous-systèmes qui étendent les fonctionnalités de base.
Le maiden projet comprend également quelques clients standards qui peuvent être utilisés immédiatement.
Enfin, le projet dispose de nombreux modules d'agent qui fournissent des fonctionnalités utiles pour créer des chatbots, etc. Eux aussi peuvent être utilisés immédiatement.