La portée d'ecr dans crystal : ou comment passer des variables et des objets dans les modèles ECR ?
En tant que débutant dans le langage Crystal, j'ai encore du mal à me faire une idée de certains de ses concepts et à me familiariser avec le codage en Crystal.
Lorsque je rencontre des problèmes difficiles, que je résous ou que je commence à comprendre, j'en parle sur mon blog, afin que d'autres puissent en profiter - car le manque de documentation est parfois sévèrement ressenti (par moi).
En avant !
Voici la documentation pour ECR (dans la v.0.27, la dernière version actuelle de Crystal) :
https://crystal-lang.org/api/0.27.0/ECR.html
Vous pouvez consulter le code source de ECR, les modèles de cristaux intégrés, ici :
https://github.com/crystal-lang/crystal/tree/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr
ECR est un langage de modèles de compilation. Vous ne pouvez pas utiliser ECR pour traiter des modèles au moment de l'exécution !
Des macros pour dominer le monde
Lorsque vous utilisez les modèles ECR dans Crystal (ou si vous les utilisez par le biais de Kemal), vous utilisez des macros.
Crystal dispose d'un langage de macros qui permet d'abstraire et de réutiliser du code. Lors de la première étape de la compilation, les macros sont évaluées et le code résultant est "collé" là où se trouvait l'appel de la macro.
Puis Crystal procède à la compilation du code.
Comment cela fonctionne-t-il pour ECR ?
Prenons, par exemple, le def_to_s(nom du fichier) dans le code de l'exemple :
exiger "ecr" classe Greeting def initialize(@name : String) fin ECR.def_to_s "greeting.ecr" fin # salutation.ecr Salutations, ! Greeting.new("John").to_s #=> Salutations, John !
Crystal va appeler la macro def_to_s au moment de la compilation avec "greeting.ecr" qui lui est passé.
La macro est définie ici :
https://github.com/crystal-lang/crystal/blob/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr/macros.cr
macro def_to_s(nom du fichier)
def to_s(__io__)
ECR.embed {{filename}}, "__io__"
fin
fin
votre classe sera réécrite comme ceci dans la première étape :
exiger "ecr" classe Greeting def initialize(@name : String) fin def to_s(__io__)
ECR.embed "greeting.ecr", "__io__" fin
Vous voyez ce qui vient de se passer ? Une méthode to_s a été ajoutée à votre classe, qui contient elle-même une macro. Voyons ce que fait cette macro :
macro embed(nom du fichier, nom du fichier)
\}{{ run("ecr/process", {{filename}}, {{io_name.id.stringify}}) }}
fin
C'est l'appel principal d'ECR. Ce qu'il fait, c'est qu'il compile ( ? / exécute) une application différente ecr/process et lui passe le nom du fichier et le nom de l'utilisateur comme paramètres.
Le retour est le résultat de la sortie de cette application.
Que signifie la barre oblique inversée ?
"Il est possible de définir un macro qui génère une ou plusieurs définitions de macro. Vous devez échapper aux expressions de la macro interne en les faisant précéder du caractère backslash \ pour éviter qu'elles ne soient évaluées par la macro externe."
Il s'agit essentiellement d'une macro imbriquée !
ecr/processus est défini ici :
il s'agit essentiellement d'une enveloppe autour de ECR.process_file (n'oubliez pas qu'il ne s'agit plus d'une macro - il s'agit d'une application dont la sortie sera éventuellement collée dans votre code Crystal !)
ecr/processeur est défini ici :
Il traite le fichier, et crée une chaîne de caractères qu'il renvoie.
En voici un petit extrait :
str << nom du tampon
str << " << "
string.inspect(str)
str << '\n'
buffer_name est ce que nous avons passé à Crystal ci-dessus (__io__ - identifié par son id dans io_name.id.stringify).
Les valeurs de sortie et de contrôle sont également traitées. De plus, la sortie de débogage est collée pour vous (en utilisant # comme commentaire) :
append_loc(str, filename, line_number, column_number)
En gros, le code que vous mettez dans les fichiers ECR est collé dans votre code directement - à l'endroit que vous avez spécifié. Tout cela est réalisé par des macros et l'exécution d'un analyseur syntaxique ECR spécial.
Le code ECR n'a pas connaissance de vos variables - si vous obtenez des échecs de portée, des échecs de variable non définie, etc., ce n'est pas parce que vous ne l'avez pas "passé dans ECR", mais parce qu'il n'est pas dans la bonne portée.
Essayez ce que vous essayez de faire avec le code ECR directement dans votre code principal.
Démonstration
Voici une démonstration de la manière d'"utiliser" une classe dans votre code ECR dans Kemal. La classe contient un snippet ECR supplémentaire qui sera rendu avec le contexte de la classe.
le fichier debug.cr :
exigez "kemal"
exiger "./../macros.cr".
module Debug
inclure les macrosclasse DClass
@test : Chaîne
def initialize()
@test = "Test String" (Chaîne de test)
fin
ECR.def_to_s "src/views/snippets/debug_snippet.ecr"
finget "/debug" do |env|
loggedin = false
mrender "debug"
finfin
le fichier debug.ecr :
<% content_for “main” do%>
Information de débogage
<%= loggedin %>
Incorporer un extrait décoré
<%= DClass.new() %>
<% end %>
le code correspondant à la macro mrender, définie dans macros.cr :
macro mrender(nom du fichier)
rendre "src/views/#{{{filename}}}.ecr", "src/views/layout.ecr".
fin
Il utilise la macro render de Kemal qui vous permet de spécifier un layout pour votre vue. (Vous n'avez pas nécessairement besoin de cela dans votre code, c'est juste donné pour être complet ici).
le fichier src/views/snippets/debug_snippet.ecr :
Extrait de débogage
.
Le résultat :
Tout mettre en place :
- L'utilisateur appelle /debug dans son navigateur web.
- La macro get de Kemal correspondra à la route "/debug". Elle est définie dans mon debug.cr
- La variable locale connecté sera mis à faux
- debug.ecr sera traité par les macros ECR et collé dans "debug.cr". (la représentation AST), comme s'il avait été directement saisi par vous.
- la variable locale connecté sera évalué comme faux (à l'exécution)
- nous appelons DClass.new()et lui demander sa représentation sous forme de chaîne - qui est définie par la méthode def to_s.
- nous pouvons appeler DClass.new(), car elle est définie dans le même module que celui dans lequel nous nous exécutons. Encore une fois, pensez simplement que votre code ECR est collé juste là, sous la définition de la classe.
- nous lui demandons sa représentation sous forme de chaîne car nous utilisons la syntaxe .
OK, regardons ce qui se passe dans l'appel DClass.new :
classe DClass
@test : Chaîne
def initialize()
@test = "Test String" (Chaîne de test)
fin
ECR.def_to_s "src/views/snippets/debug_snippet.ecr"
fin
à l'initialisation, une chaîne de caractères @test est définie. Il s'agit d'un instance de l'instance de DClass que nous venons de créer. (Vous pouvez voir qu'il s'agit d'une variable d'instance car elle est précédée d'un "@". Deux "@@" seraient une variable de classe)
Cette variable d'instance est utilisée / affichée dans debug_snippet.ecr
Nous avons déjà discuté du fonctionnement de ECR.def_to_s.
En fait, après être passée par le stade de la macro, cette classe ressemblerait à quelque chose comme ceci :
classe DClass
@test : Chaîne
def initialize()
@test = "Test String" (Chaîne de test)
fin
def to_s(__io__)
__io__ << "Extrait de débogage\n"
__io__ << ""
__io__ << @test
__io__ << ""
fin
fin
Grâce à cette technique, vous pouvez définir des classes pour rendre des extraits de code ECR, au lieu de configurer et de transmettre chaque nom de variable manuellement.
J'espère que cet article vous aidera et que vous y trouverez votre compte au lieu de vous sentir frustré. J'aurais aimé avoir quelque chose comme ça pour me guider dans mes débuts.
Références
Référence Kemal :
Reportez-vous à cette page pour en savoir plus sur les macros :
NB : Nœuds AST : nœuds d'arbre de syntaxe abstraite
- https://crystal-lang.org/reference/syntax_and_semantics/methods_and_instance_variables.html
- https://crystal-lang.org/reference/syntax_and_semantics/class_variables.html
Cela m'a mis dans la bonne direction, bravo !