El alcance de ecr dentro de crystal: o ¿cómo puedo pasar variables y objetos a las plantillas de ECR?

Como principiante en el lenguaje Crystal, todavía me cuesta entender algunos de los conceptos que contiene y desarrollar una sensación de codificación en Crystal.

Cuando me encuentro con problemas difíciles, que resuelvo o empiezo a entender, escribo un blog sobre ellos, para que otros puedan beneficiarse - ya que la falta de documentación se siente gravemente (por mí) a veces.

¡Adelante!

Aquí está la documentación de ECR (en la v.0.27, la última versión actual de Crystal):

https://crystal-lang.org/api/0.27.0/ECR.html

Puedes echar un vistazo al código fuente de ECR, las plantillas de cristal incrustado, aquí:

https://github.com/crystal-lang/crystal/tree/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr

ECR es un lenguaje de plantillas en tiempo de compilación. No se puede utilizar ECR para procesar plantillas en tiempo de ejecución.

Macros para gobernar el mundo

Cuando se utilizan las plantillas ECR en Crystal ( también si se utilizan a través de Kemal ), se están utilizando macros.

Crystal dispone de un lenguaje de macros que permite abstraer y reutilizar el código; como primer paso de la compilación, las macros se evalúan y el código resultante se "pega" en el lugar donde ha estado la llamada a la macro.

Luego Crystal pasa a compilar el código.

¿Cómo funciona esto para la ECR?

Tomemos, por ejemplo, el def_to_s(nombre_de_archivo) en el código del ejemplo:

requieren "ecr"

class Saludo
  def initialize(@nombre : Cadena)
  end

  ECR.def_to_s "saludo.ecr"
end

# saludo.ecr
¡Saludo, !

Greeting.new("Juan").to_s #=> ¡Saludos, Juan!

Crystal llamará a la macro def_to_s en tiempo de compilación con "greeting.ecr" que se pasa en él.
La macro se define aquí:
https://github.com/crystal-lang/crystal/blob/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr/macros.cr

macro def_to_s(nombre de archivo)

  def to_s(__io__)

     ECR.embed {{filename}}, "__io__"

  fin

fin

su clase se reescribirá así en el primer paso:

requieren "ecr"

class Saludo
  def initialize(@nombre : Cadena)
  end

  def to_s(__io__)
    ECR.embed "saludo.ecr", "__io__"
fin

¿Ves lo que acaba de pasar? se ha añadido un método to_s a tu clase, que a su vez contiene una macro. Veamos lo que hace esta macro:

macro incrustar(nombre_de_archivo, io_nombre)

    \{{ruta("ecr/proceso", {{nombre_del_archivo}}, {{nombre_del_archivo}}) }}

fin

Esta es la llamada central de ECR. Lo que hace, es que compila (? / ejecuta) una aplicación diferente ecr/proceso y le pasa como parámetros el nombre del archivo y el nombre_de_la_iniciativa.

El retorno es el resultado de la salida de esa aplicación.

¿Qué significa la barra invertida?

"Es posible definir un que genera una o más definiciones de macros. Debe escapar de las expresiones de macro de la macro interna precediéndolas con un carácter de barra invertida \ para evitar que sean evaluadas por la macro externa."

Es esencialmente una macro anidada.

ecr/proceso se define aquí:

https://github.com/crystal-lang/crystal/blob/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr/process.cr

es esencialmente una envoltura alrededor de ECR.process_file (recuerde, esto ya no es una macro - es una aplicación cuya salida será eventualmente pegada en su código Crystal)

ecr/procesador se define aquí:

https://github.com/crystal-lang/crystal/blob/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr/processor.cr

Procesa el archivo y crea una cadena que devuelve.

He aquí un pequeño extracto:

str << nombre_del_buffer

str << " << "

string.inspect(str)

str << '\n'

buffer_name es lo que le pasamos a Crystal arriba (__io__ - identificado por su id en io_name.id.stringify).

También se procesan los valores de salida y de control. Además, la salida de depuración se pega para usted (usando # como comentario):

append_loc(str, filename, line_number, column_number)

Básicamente, el código que pones en los archivos ECR se pega en tu código directamente, en el lugar que has especificado. Todo se hace mediante macros y ejecutando un analizador especial de ECR.

El código de la ECR no está al tanto de tus variables - si obtienes fallos de ámbito, fallos de variables indefinidas, etc, no se debe a que no "lo hayas pasado a la ECR", sino a que no está en el ámbito correcto.

Prueba lo que intentabas hacer con el código ECR directamente en tu código principal.

Demostración

Aquí hay una demostración de cómo "usar" una clase dentro de su código ECR en Kemal. La clase anida un fragmento de ECR adicional para ser renderizado con el contexto de las clases.

el archivo debug.cr:

requieren "kemal"

require "./../macros.cr"

módulo Debug
   incluir Macros

      clase DClass
           @prueba : Cadena
           def inicializar()
               @test = "Cadena de prueba"
           fin
           ECR.def_to_s "src/views/snippets/debug_snippet.ecr"
       fin

  get "/debug" do |env|

    logueado = false

    mrender "debug"
   fin

fin

el archivo debug.ecr:

<% content_for “main” do%>

Información de depuración

<%= loggedin %>

Incorporación de un fragmento decorado

<%= DClass.new() %>

<% end %>

el código correspondiente a la macro mrender, definida en macros.cr:

macro mrender(filename)
   render "src/views/#{{filename}}.ecr", "src/views/layout.ecr"
fin   

Utiliza la macro render de Kemal que le permite especificar un diseño para su vista. (No necesitas esto en tu código necesariamente, sólo se da para completarlo aquí)

el archivo src/views/snippets/debug_snippet.ecr:

Fragmento de depuración

La salida:

imagen

Ponerlo todo junto:

  1. El usuario llama a /debug en su navegador web
  2. Kemal 's macro get coincidirá para la ruta "/debug". Está definida en mi debug.cr
    1. La variable local conectado se establecerá en falso
    2. debug.ecr será procesado por las macros ECR, y pegado en "debug.cr" (la representación AST), como si fuera introducido directamente por usted
      1. la variable local conectado se evaluará como falso (en tiempo de ejecución)
      2. llamamos DClass.new()y pedirle su representación en forma de cadena, que se define con el método def to_s.
        1. podemos llamar a DClass.new(), porque está definida en el mismo módulo en el que estamos ejecutando. Una vez más, simplemente piense como su código ECR se pega allí mismo, debajo de la definición de la clase.
        2. le pedimos su representación en forma de cadena porque utilizamos la sintaxis .

Bien, echemos un vistazo a lo que ocurre en la llamada DClass.new:

      clase DClass
          @prueba : Cadena
           def inicializar()
               @test = "Cadena de prueba"
           fin
           ECR.def_to_s "src/views/snippets/debug_snippet.ecr"
       fin

en la inicialización, se establece una cadena @test. Esta es una instancia de la instancia de DClass que acabamos de crear. (Puedes ver que es una variable de instancia porque tiene una "@" delante. Dos "@@" serían una variable de clase)

Esta variable de instancia se utiliza / muestra en debug_snippet.ecr

Ya hemos hablado de cómo funciona ECR.def_to_s.

Efectivamente, después de pasar por la fase de macro, esta clase tendría un aspecto similar al siguiente:

      clase DClass
          @prueba : Cadena
           def inicializar()
               @test = "Cadena de prueba"
           fin
           def to_s(__io__)

                __io__ << "Fragmento de depuración\n"

                __io__ << ""

                __io__ << @prueba

                <_io__ << ""

           fin
       fin

Utilizando esta técnica puedes definir clases para renderizar fragmentos de código ECR, en lugar de configurar y pasar cada nombre de variable manualmente.

Espero que este artículo te ayude, y que lo encuentres en lugar de frustrarte - ojalá tuviera algo así para guiarme en mis inicios Sonrisa

Referencias

Referencia de Kemal:

Consulte esta página para obtener más información sobre las macros:

NB: Nodos AST: nodos del árbol de sintaxis abstracta

Esto me indicó la dirección correcta, ¡felicitaciones!