Skip to content

ReLaX (Rendering Enviroment for LaTeX) es un ecosistema de renderizado de plantillas para la automatización de creación de documentos en base a plantillas con LaTeX.

License

Notifications You must be signed in to change notification settings

AgustinDoCanto/ReLaX

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ReLaX

ReLaX (Rendering Enviroment for LaTeX) es un entorno de renderizado para LaTeX permitiendo la automatización de creación de documentos a través de plantillas.

relax-version-image.png

Código fuente:

GitHub

Instalación

Mediante PyPi (pip)

Para instalar ReLaX mediante los repositorios de PyPi (pip) puedes seguir los siguientes pasos:

Crear una carpeta para el proyecto:

mkdir <project_name>
cd <project_name>

Crear un entorno virtual y activarlo:

python -m venv venv
source venv/bin/activate

Instalar mediante pip la libreria:

pip install doc-relax

Para verificar la correcta instalación y funcionamiento puede correr el comando:

relax version

NOTA: Es importante contar con pdflatex instalado correctamente así como toda la paqueteria necesaria para compilar los archivos .tex de manera local. De otra forma puedes optar por la instalación Mediante Dockerfile que encapsula todos los recursos necesarios para un desarrollo de documentos básico, dentro de un contenedor.

Mediante Dockerfile (Virtualizado)

Se provee un archivo Dockerfile que permite desplegar una version del ecosistema ReLaX con lo básico para el desarrollo:

Para trabajar mediante contenedores primero se debe crear una carpeta donde se va realizar el proyecto con ReLaX:

mkdir <nombre_de_carpeta>

Posteriormente nos situamos dentro de la misma:

cd <nombre_de_carpeta>

NOTA: Para la siguiente sección es necesario tener instalado y corriendo correctamente el demonio de docker (en Linux) o docker-desktop abierto (en Windows).

Copiamos dentro de la carpeta recientemente creada el archivo Dockerfile de este repositorio y posteriormente situados dentro de ese directorio realizamos el build del contenedor con:

docker build -t <nombre_del_contenedor> .

Una vez creado el contenedor podemos ejecutarlo en modo interactivo para empezar con el desarrollo del proyecto con:

En Linux:

docker run -it --rm -v "$(pwd)":/<nombre_de_la_carpeta> <nombre_del_contenedor>

o tambien

docker run -it --rm -v $PWD:/relax-docker relax-local

En Windows (Powershell)

docker run -it --rm -v ${PWD}:/<nombre_de_la_carpeta> <nombre_del_contenedor>

En Git bash

docker run -it --rm -v "$(pwd -W):/<nombre_de_la_carpeta>" <nombre_del_contenedor>

Esto correra una consola interactiva con el contenedor que te permitira crear directorios y utilizar ReLaX a través de la carpeta montada como volumen.

Para salir de la misma simplemente escribe exit.

ReLaX commands

Los comandos se ejecutan directamente en la shell

Ver comandos disponibles

relax --help

Ver version de ReLaX

relax version

Crear un nuevo proyecto

relax new -p <project_name>

O tambien

relax new --project <project_name>

Esto crea una carpeta con la estructura basica del proyecto

Crear un nuevo Componente

relax create -c <component_name>

O tambien

relax create --project <component_name>

Esto crea un componente con el nombre especificado

Compilar el documento ReLaX a PDF

ReLaX hace uso de 'pdflatex' para compilar el documento, por lo que es necesario tenerlo instalado correctamente

relax build --project <project_name>

O tambien

relax build --project <project_name> --clean <boolean>

La flag "--clean" o "-c" en el comando "build" suprime la generacion de los archivos: "aux","log", "out", "toc" si esta en "True" y permite la generacion si esta en "False"

IMPORTANTE: Para correr el comando relax build se necesita apuntar a el directorio que contiene al proyecto.

Utilizacion de Componentes

Al crear un componente con el comando "relax create" se crea una carpeta dentro del proyecto que contiene el archivo del componente .py y el archivo .tex:

relax create -c ExampleComponent

Creara el siguiente arbol de subdirectorios:

ExampleComponent

  • ExampleComponent.py
  • ExampeComponent.tex

El archivo .py esta destinado a todas las operaciones, importaciones, o conexiones con otros modulos o componentes relacionadas a python mientras que el .tex se encarga de presentar la plantilla de como se renderizaran esos resultados cada vez que se utilize el componente.

El archivo .py y .tex se conectan mediante el diccionario de retorno en el archivo .py, en el mismo se definen las variables o datos que se vayan a retornar (si es que se retorna alguno).

Static Components (Componentes Estáticos)

Los componentes estaticos son componentes que no reciben ningun parametro externo y solo se encargan de renderizar codigo LaTeX en especifico.

Un ejemplo de creacion de un componente estatico seria el siguiente:

Creacion del componente

relax create -c FirstExampleComponent

Luego de la creacion del componente, el directorio deberia verse asi:

FirstExampleComponent.py

from RelaxCore import Component

@Component(template="FirstExampleComponent.tex")
def FirstExampleComponent(**kargs):
	return kargs

FirstExampleComponent.tex

FirstExampleComponent works!

Y el archivo main asi (ya que aun no incluimos el componente)

main.py

from RelaxCore import Component

@Component(template="main.tex")
def main(**kargs):
	return kargs

Inclusion del componente

Este primer componente se encargará de imprimir "Esto es un texto en Italica" cada vez que se llame al componente, para esto haremos los siguientes cambios en el proyecto:

En FirstExampleComponent

FirstExampleComponent.py

from RelaxCore import Component

@Component(template="FirstExampleComponent.tex")
def FirstExampleComponent(**kargs):
	return kargs

FirstExampleComponent.tex

\textit{Esto es un texto en italica} \newline

En main

Incluimos el componente en archivo main

main.py

from FirstExampleComponent.FirstExampleComponent import FirstExampleComponent
from RelaxCore import Component

@Component(template="main.tex")
def main(**kargs):
	return { 'FirstExampleComponent' : FirstExampleComponent }

Llamamos al componente dentro del main.tex

main.tex

\documentclass{article}

\usepackage[utf8]{inputenc} % Codificación UTF-8
\usepackage[T1]{fontenc}    % Codificación de fuentes
\usepackage{lipsum}         % Texto de relleno opcional

\title{Título del Documento}
\author{Autor}
\date{\today}

\begin{document}

\maketitle

<< FirstExampleComponent() >> %Llamamos a FirstExampleComponent


\end{document}

Posteriormente renderizamos el documento con:

relax build -p <path_to_project> -c <boolean>

Con esto el texto "Esto es un texto en italica" deberia aparecer en nuestro documento PDF.

De esta manera haz renderizado tu primer componente estático en ReLaX.

Dinamic Components (Componentes Dinámicos)

Si bien los componentes estáticos son de gran utilidad para encapsular grandes cantidades de texto y tambien nos permiten reutilizar texto preformateado aumentando asi la legibilidad y mantenibilidad del codigo tambien nos limitan a usar siempre el mismo texto o las mismas etiquetas no permitiendo variar el contenido del componente a nuestro gusto, es aqui donde entran los componentes dinámicos.

Los componentes dinamicos son idénticos a los estáticos con la diferencia de que estos permiten el ingreso de variables en base a las cuales el componente podrá ejecutar lógica interna y renderizar el contenido dependiendo de la entrada.

Creación del Componente

El comando para crear un componente dinámico es el mismo que para un estático ya que la diferencia es lógica, no física (no código):

relax create -c SecondExampleComponent

Siguiendo el ejemplo anterior haremos que este componente renderize el texto recibido a través de una variable, en italica, para esto el código seria el siguiente:

En SecondExampleComponent

SecondExampleComponent.py

from RelaxCore import Component

@Component(template="SecondExampleComponent.tex")
def SecondExampleComponent(**kargs):
	return {
		'textToRender' : None
	}

SecondExampleComponent.tex

\textit{<< textToRender >>} \newline

En main

Incluimos el componente en archivo main, cabe destacar que tambien esta incluido FirstExampleComponent del ejemplo anterior

main.py

from FirstExampleComponent.FirstExampleComponent import FirstExampleComponent
from SecondExampleComponent.SecondExampleComponent import SecondExampleComponent
from RelaxCore import Component

components_to_include = { 
'FirstExampleComponent' : FirstExampleComponent,
'SecondExampleComponent' : SecondExampleComponent 
}

@Component(template="main.tex")
def main(**kargs):
	return components_to_include

Llamamos al componente dentro del main.tex

main.tex

\documentclass{article}

\usepackage[utf8]{inputenc} % Codificación UTF-8
\usepackage[T1]{fontenc}    % Codificación de fuentes
\usepackage{lipsum}         % Texto de relleno opcional

\title{Título del Documento}
\author{Autor}
\date{\today}

\begin{document}

\maketitle

<< FirstExampleComponent() >> %Llamamos a FirstExampleComponent

<< SecondExampleComponent(textToRender="Esto es un dinámico") >> % Renderiza el texto ingresado, en este caso: "Esto es un dinámico"

\end{document}

Al buildear el documento en el PDF deberia haberse agregado la linea "Esto es un texto dinámico" también en italicas.

La ventaja de este nuevo tipo de componentes radica en su reutilización y posibilidad de calcular lógica interna en base a variables.

main.tex

\documentclass{article}

\usepackage[utf8]{inputenc} % Codificación UTF-8
\usepackage[T1]{fontenc}    % Codificación de fuentes
\usepackage{lipsum}         % Texto de relleno opcional

\title{Título del Documento}
\author{Autor}
\date{\today}

\begin{document}

\maketitle

% Llamamos 3 veces a FirstExampleComponent
<< FirstExampleComponent() >> 
<< FirstExampleComponent() >> 
<< FirstExampleComponent() >> 
% En todos los casos renderizara en el documento "Esto es un texto en italicas"


% Mientras que si hacemos distintas llamadas con SecondExampleComponent
<< SecondExampleComponent(textToRender="Esto es un dinámico") >> % Renderiza el texto ingresado, en este caso: "Esto es un dinámico"
<< SecondExampleComponent(textToRender="Me encanta usar ReLaX") >> % Renderiza el texto ingresado, en este caso: "Me encanta usar ReLaX"
% Renderiza textos distindos dependiendo de la entrada
\end{document}

Con los componentes dinámicos no estamos limitados a utilizar una única variable, sino que podemos hacer uso de varias o incluir valores por defecto en ellas que nos avisen, por ejemplo, de que no incluimos un valor al llamar al componente.

Un claro ejemplo de esto seria un componente para incluir imagenes o figuras dentro del documento:

relax create -c ImageComponent

ImageComponent.py

from RelaxCore import Component

@Component(template="ImageComponent.tex")
def ImageComponent(**kargs):
	return {
		'source': "[!]SOURCE REQUIRED[!]", 
		'scale': '[!]SCALE REQUIRED[!]',
		'label': None,
		'caption': None
	}

ImageComponent.tex

\begin{figure}
    \centering
    \includegraphics[scale=<< scale >>]{<< source >>}<$ if caption $>
    \caption{<< caption >>}<$ endif $><$ if label $>
    \label{<< label >>}<$ endif $>
\end{figure}

Nota: El uso de la tag if se especifica más adelante, en este caso la tag se utiliza para incluir la linea si se ingresa algún dato y no incluirla si no.

main.py

from FirstExampleComponent.FirstExampleComponent import FirstExampleComponent
from SecondExampleComponent.SecondExampleComponent import SecondExampleComponent
from ImageComponent.ImageComponent import ImageComponent
from RelaxCore import Component

components_to_include = { 
	'FirstExampleComponent' : FirstExampleComponent,
	'SecondExampleComponent' : SecondExampleComponent, 
	'ImageComponent' : ImageComponent 
}

@Component(template="main.tex")
def main(**kargs):
	return components_to_include

main.tex

\documentclass{article}

\usepackage[utf8]{inputenc} % Codificación UTF-8
\usepackage[T1]{fontenc}    % Codificación de fuentes
\usepackage{lipsum}         % Texto de relleno opcional
\usepackage{graphicx}

\title{Título del Documento}
\author{Autor}
\date{\today}

\begin{document}

\maketitle

% Llamamos 3 veces a FirstExampleComponent
<< FirstExampleComponent() >> 
<< FirstExampleComponent() >> 
<< FirstExampleComponent() >> 
% En todos los casos renderizara en el documento "Esto es un texto en italicas"


% Mientras que si hacemos distintas llamadas con SecondExampleComponent
<< SecondExampleComponent(textToRender="Esto es un dinámico") >> % Renderiza el texto ingresado, en este caso: "Esto es un dinámico"
<< SecondExampleComponent(textToRender="Me encanta usar ReLaX") >> % Renderiza el texto ingresado, en este caso: "Me encanta usar ReLaX"
% Renderiza textos distindos dependiendo de la entrada


<< ImageComponent(scale="1.0", source="Img/cat_image.jpg") >> % ImageComponent recibiendo la escala de uno y una imagen

<< ImageComponent(scale="1.0", source="Img/cat_image.jpg", caption="Caption de imagen generica", label="Etiqueta") >> % Imagen con escala, directorio fuente y caption

\end{document}

Luego de compilar el codigo .tex dentro de output, la parte correspondiente a las imagenes, se veria asi:

\begin{figure}
    \centering
    \includegraphics[scale=1.0]{Img/cat_image.jpg}
\end{figure} % ImageComponent recibiendo la escala de uno y una imagen

\begin{figure}
    \centering
    \includegraphics[scale=1.0]{Img/cat_image.jpg}
    \caption{Caption de imagen generica}
    \label{Etiqueta}
\end{figure} % Imagen con escala, directorio fuente y caption

Jinja Tags (Etiquetas de Jinja)

ReLaX hace uso de la herramienta de templating "Jinja 2" la cual permite crear templates de documentos para rellenarlos en base a una lógica o dinámicamente.

Hacer uso de esta herramienta le permite a ReLaX extender sus capacidades mediante las tags de la misma lo que puede ser de ayuda en el caso de querer renderizar Componentes de manera dinámica o en base a una lógica (como se ejemplifico en el Componente ImageComponent).

Las tags se denotan entre '<$' y '$>'.

Conditional Tags (Tags condicionales)

Las etiquetas condicionales permiten evaluar expresiones y controlar el flujo de ejecución en una plantilla. Las principales etiquetas condicionales son:

1. if

Evalúa una condición y ejecuta el bloque si es verdadera.

<$ if condicion $>
    Este texto se renderizará si se cumple la condicion
<$ endif $>

2. if-else

Evalúa una condición y ejecuta el bloque if si es verdadera, ejecuta el bloque else si no.

<$ if condicion $>
    Este texto se renderizará si se cumple la condición
<$ else $>
	Este texto se renderizará si no se cumple la condición
<$ endif $>

2. if-elif-else

Evalúa determinadas condiciones y ejecuta el bloque dependiendo de la cual condición se cumpla, si ninguna se cumple entonces ejecuta el bloque else.

<$ if primera_condicion $>
    Solo este texto se renderizará si la primera condicion se cumple.
<$ elif segunda_condicion $>
	Solo este texto se renderizará si la segunda condicion se cumple.
<$ else $>
	Este texto se renderizará si no se cumple ninguna condición.
<$ endif $>

Conditional Operators (Operadores condicionales)

En ReLaX se puede hacer uso de los operadores para hacer comparaciones dentro de las condiciones:

  • Comparación: ==, !=, <, >, <=, >=
  • Lógicos: and, or, not
  • Pertenencia: in, not in
  • Existencia: is defined, is not defined, is none

Loop Tags (Tags de bucles)

Las tags de bucle permiten iterar sobre una condición o hasta que la misma se cumpla, esto permite llamar N veces a un Componente de una manera muy cómoda. Para esto se utiliza la tag "for":

<$ for item in items $>
	Contenido dentro del bucle
<$ endfor $>

Por ejemplo, si quisieramos agregar 10 veces 'cat_image.jpg' en nuestro documento bastaria con un bucle for que iterase sobre una lista con la ruta de "cat_image.jpg" repetida 10 veces, cabe destacar que esa lista podria contener no solo la ruta de una imagen sino de varias, permitiendo asi la renderización de un lote de imagenes de manera directa.

Si seguimos trabajando con el ejemplo de Componente ImageComponent:

main.py

from FirstExampleComponent.FirstExampleComponent import FirstExampleComponent
from SecondExampleComponent.SecondExampleComponent import SecondExampleComponent
from ImageComponent.ImageComponent import ImageComponent
from RelaxCore import Component


image_path = "component-examples/Img/cat_image.jpg"
images_list = []
for _ in range(10):
	images_list.append(image_path)


dictionary_of_includes = { 
	'FirstExampleComponent' : FirstExampleComponent,
	'SecondExampleComponent' : SecondExampleComponent, 
	'ImageComponent' : ImageComponent,
	'images_list': images_list 
}

@Component(template="main.tex")
def main(**kargs):
	return dictionary_of_includes

main.tex

\documentclass{article}

\usepackage[utf8]{inputenc} % Codificación UTF-8
\usepackage[T1]{fontenc}    % Codificación de fuentes
\usepackage{lipsum}         % Texto de relleno opcional
\usepackage{graphicx}
\usepackage{float}

\title{Título del Documento}
\author{Autor}
\date{\today}

\begin{document}

\maketitle


% Incluimos las imagenes de images_list
<$ for image in images_list $>
	<<ImageComponent(scale="1.0", source=image) >>
<$ endfor $>

\end{document}

Este código renderiza la imagen correspondiente al path indicado 10 veces dentro del documento, en este caso "cat_image.jpg"

Macro Tags

Las Tags Macro de Jinja 2 permiten al usuario definir "Macros" que actuan como un conjunto de instrucciones que se ejecutan en conjunto con una llamada, similar a auna función. Las mismas se definen de la siguiente forma

 <$ macro NombreMacro() $> % Declaración de una macro sin parametros de entrada
 	Al llamar esta macro, se imprimirá este texto!
 <$ endmacro $>

 <$ macro NombreMacro(parametro1, parametro2,etc) $> % Declaración de una macro con parametros de entrada
 	Al llamar a esta macro imprimiremos este texto con << parametro1 >>, << parametro2 >>
 <$ endmacro $>

Las macros son una potente herramienta ya que nos permiten encapsular código dentro de las mismas similar a como lo hariamos con un componente, veamos un ejemplo:

Creamos el componente

relax create -c MacroComponentExample	

Definimos el código dentro del mismo

MacroComponentExample.py

from RelaxCore import Component

@Component(template="MacroComponentExample.tex")
def MacroComponentExample(**kargs):
	return kargs

MacroComponentExample.tex

<$ macro MacroWithoutParameters() $>
	\textit{Este texto se renderiza desde la macro MacroWithoutParameters}
<$ endmacro $>

Al importar y utilizar esta macro en main.tex se renderizara el texto en italica "Este texto se renderiza desde la macro MacroWithoutParameters":

main.py

from FirstExampleComponent.FirstExampleComponent import FirstExampleComponent
from SecondExampleComponent.SecondExampleComponent import SecondExampleComponent
from ImageComponent.ImageComponent import ImageComponent
from MacroComponentExample.MacroComponentExample import MacroComponentExample
from RelaxCore import Component


dictionary_of_includes = { 
	'FirstExampleComponent' : FirstExampleComponent,
	'SecondExampleComponent' : SecondExampleComponent, 
	'ImageComponent' : ImageComponent,
	'MacroComponentExample' : MacroComponentExample
}

@Component(template="main.tex")
def main(**kargs):
	return dictionary_of_includes

main.tex

% Debemos importar la macro MacroWithoutParameter desde el componente
<$ from "MacroComponentExample/MacroComponentExample.tex" import MacroWithoutParameters $> 

\documentclass{article}

\usepackage[utf8]{inputenc} % Codificación UTF-8
\usepackage[T1]{fontenc}    % Codificación de fuentes
\usepackage{lipsum}         % Texto de relleno opcional
\usepackage{graphicx}
\usepackage{float}

\title{Título del Documento}
\author{Autor}
\date{\today}

\begin{document}

\maketitle

<< MacroWithoutParameters() >>

\end{document}

Como se puede visualizar en main.tex siempre es necesario importar las macros creadas en el componente para poder usarlas.

Macros con parametros

El mecanismo de macros de Jinja 2 permite recibir parámetros en su definición que nos permiten utilizar los mismos más tarde. Estos parámetros pueden ser de diversos tipos, incluyendo otras macros lo que habilita la interpolación de strings (concatenar texto con el resultado de ejecutar una macro), veamos un ejemplo:

Creamos el componente

relax create -c MacroWithParameters

MacroWithParameters.py

from RelaxCore import Component

@Component(template="MacroWithParameters.tex")
def MacroWithParameters(**kargs):
	return kargs

MacroWithParameters.tex

<$- macro ShowMessage(text) -$> % La macro ShowMessage muestra el texto recibido
<< text >>
<$- endmacro -$>

<$- macro TextBold(text) -$>
\textbf{<< text >>}
<$- endmacro -$>

NOTA: Los guiones en las etiquetas sirven para eliminar el espacio que dejan en blanco al remplazar las tags por espacios en blanco, pero se puede prescindir de ellas.

main.py

from FirstExampleComponent.FirstExampleComponent import FirstExampleComponent
from SecondExampleComponent.SecondExampleComponent import SecondExampleComponent
from ImageComponent.ImageComponent import ImageComponent
from MacroComponentExample.MacroComponentExample import MacroComponentExample
from MacroWithParameters.MacroWithParameters import MacroWithParameters
from RelaxCore import Component


dictionary_of_includes = { 
	'FirstExampleComponent' : FirstExampleComponent,
	'SecondExampleComponent' : SecondExampleComponent, 
	'ImageComponent' : ImageComponent,
	'MacroComponentExample' : MacroComponentExample,
	'MacroWithParameters' : MacroWithParameters
}

@Component(template="main.tex")
def main(**kargs):
	return dictionary_of_includes

main.tex

<$ from "MacroWithParameters/MacroWithParameters.tex" import ShowMessage $>
<$ from "MacroWithParameters/MacroWithParameters.tex" import TextBold $>

\documentclass{article}

\usepackage[utf8]{inputenc} % Codificación UTF-8
\usepackage[T1]{fontenc}    % Codificación de fuentes
\usepackage{lipsum}         % Texto de relleno opcional
\usepackage{graphicx}
\usepackage{float}

\title{Título del Documento}
\author{Autor}
\date{\today}

\begin{document}

\maketitle

<< ShowMessage("Este es un texto normal que se renderizará comunmente") >> % Pasamos el texto como parametro a ShowMessage

\end{document}

El primer "ShowMessage" se encarga de mostrar "Este es un texto normal que se renderizará comunmente" pasado como parámetro a la macro, dentro del documento.

Interpolación de parametros y macros

<$ from "MacroWithParameters/MacroWithParameters.tex" import ShowMessage $>
<$ from "MacroWithParameters/MacroWithParameters.tex" import TextBold $>

\documentclass{article}

\usepackage[utf8]{inputenc} % Codificación UTF-8
\usepackage[T1]{fontenc}    % Codificación de fuentes
\usepackage{lipsum}         % Texto de relleno opcional
\usepackage{graphicx}
\usepackage{float}

\title{Título del Documento}
\author{Autor}
\date{\today}

\begin{document}

\maketitle

<< ShowMessage("Este es un texto que incluye el siguiente texto pero en negrita " ~ TextBold("Este es el texto que debe estar en negrita") ~ ", Aqui finalizamos la cadena") >> % Interpolamos el texto que recibe ShowMessage con TextBold

\end{document}

En el segundo componente se logra algo similar, pero se interpola la llamada a la macro "TextBold" con la cual logramos renderizar texto en negrita dentro de texto normal, esto se puede generalizar para cualquier tipo de macro y cualquier parámetro del mismo.

Diferencias entre Macro Tags y Componentes ReLaX

La principal diferencia entre las Macro Tags y los Componentes ReLaX es que los componentes son funciones python mientras que las Macros actuan de acuerdo al funcionamiento interno de Jinja 2, pero ambos poseen mecanismos similares, por lo que la interpolación de texto y parametros dentro y otras funcionalidades de las macros también son utilizables con los Componentes de ReLaX.

Para profundizar sobre algunas características avanzadas de "Jinja 2" como herencia de plantillas o funcionamiento interno de macros, puede visitar la documentación oficial que detalla el sistema de herencia de plantillas y tags a profundidad.

About

ReLaX (Rendering Enviroment for LaTeX) es un ecosistema de renderizado de plantillas para la automatización de creación de documentos en base a plantillas con LaTeX.

Resources

License

Stars

Watchers

Forks

Packages

No packages published