Skip to content

Commit 9bc0d01

Browse files
authored
Enhance Template Handling with Jinja2 Support and Custom Filters (#4)
#### Overview This PR introduces significant enhancements to the template processing capabilities by integrating Jinja2 for more flexible and powerful templating. Additionally, new custom filters have been added to extend the functionality, specifically for fetching the latest GitHub release. #### Changes - **Jinja2 Integration**: Replaced the existing string templating mechanism with Jinja2, allowing for more complex templating options. - **New Template Variables**: Added support for default template variables like `file_name` and `file_directory`. - **Custom Filters**: Introduced a new custom Jinja2 filter `latest_release` that fetches the latest release version from a GitHub repository. - **Updated Documentation**: Enhanced the README files (`README.md` and `README.es.md`) with detailed information on how to use the new templating features, including variables and custom filters. - **Configuration Updates**: Extended the `generic-app.yaml` and `structure.yaml` examples to demonstrate the use of the new templating features. - **Dependencies**: Added `jinja2` and `PyGithub` to `requirements.txt` to support the new templating and GitHub API functionalities. #### Justification These changes provide more robust and flexible template processing, enabling users to create more dynamic and maintainable configurations. The new features simplify complex templating scenarios and extend the capability to interact with external APIs, like GitHub, directly within the templates. #### Impact - **Developers**: Improved ability to create and manage complex templates with dynamic content. - **Project Maintenance**: Enhanced documentation will make it easier for new contributors to understand and utilize the templating system. - **Dependencies**: Additional dependencies (`jinja2` and `PyGithub`) will be required, which might affect environments where this project is used.
1 parent b248b8a commit 9bc0d01

7 files changed

Lines changed: 161 additions & 13 deletions

File tree

README.es.md

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,20 +141,51 @@ Aquí tienes un ejemplo de un archivo de configuración YAML:
141141
structure:
142142
- README.md:
143143
content: |
144-
# ${project_name}
145-
Este es un repositorio de plantilla.
144+
# {{@ project_name @}}
145+
This is a template repository.
146146
- script.sh:
147147
permissions: '0777'
148148
content: |
149149
#!/bin/bash
150-
echo "¡Hola, ${author_name}!"
150+
echo "Hello, {{@ author_name @}}!"
151151
- LICENSE:
152152
file: https://raw.githubusercontent.com/nishanths/license/master/LICENSE
153153
- src/main.py:
154154
content: |
155-
print("¡Hola, Mundo!")
155+
print("Hello, World!")
156156
```
157157
158+
### Variables de plantilla
159+
160+
Puedes usar variables de plantilla en tu archivo de configuración encerrándolas entre `{{@` y `@}}`. Por ejemplo, `{{@ project_name @}}` será reemplazado con el valor de la variable `project_name` en tiempo de ejecución.
161+
162+
Si necesitas definir bloques, puedes usar la notación de inicio de bloque `{%@` y la notación de final de bloque `%@}`.
163+
164+
Para definir comentarios, puedes usar la notación de inicio de comentario `{#@` y la notación de fin de comentario `@#}`.
165+
166+
#### Variables de plantilla predeterminadas
167+
168+
- `file_name`: El nombre del archivo que se está procesando.
169+
- `file_directory`: El nombre del directorio del archivo que se está procesando.
170+
171+
#### Filtros personalizados de Jinja2
172+
173+
##### `latest_release`
174+
175+
Este filtro obtiene la versión más reciente de una release en un repositorio de GitHub. Toma el nombre del repositorio como argumento.
176+
177+
```yaml
178+
structure:
179+
- README.md:
180+
content: |
181+
# MyProject
182+
Latest release: {{@ "httpdss/struct" | latest_release @}}
183+
```
184+
185+
Esto utiliza PyGithub para obtener la última release del repositorio, por lo que configurar la variable de entorno `GITHUB_TOKEN` te dará acceso a repositorios privados.
186+
187+
Si ocurre un error en el proceso, el filtro devolverá `LATEST_RELEASE_ERROR`.
188+
158189
## 👩‍💻 Desarrollo
159190

160191
Para comenzar con el desarrollo, sigue estos pasos:

README.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,20 +146,51 @@ Here is an example of a YAML configuration file:
146146
structure:
147147
- README.md:
148148
content: |
149-
# ${project_name}
149+
# {{@ project_name @}}
150150
This is a template repository.
151151
- script.sh:
152152
permissions: '0777'
153153
content: |
154154
#!/bin/bash
155-
echo "Hello, ${author_name}!"
155+
echo "Hello, {{@ author_name @}}!"
156156
- LICENSE:
157157
file: https://raw.githubusercontent.com/nishanths/license/master/LICENSE
158158
- src/main.py:
159159
content: |
160160
print("Hello, World!")
161161
```
162162
163+
### Template variables
164+
165+
You can use template variables in your configuration file by enclosing them in `{{@` and `@}}`. For example, `{{@ project_name @}}` will be replaced with the value of the `project_name` variable at runtime.
166+
167+
If you need to define blocks you can use starting block notation `{%@` and end block notation `%@}`.
168+
169+
To define comments you can use the comment start notation `{#@` and end comment notation `@#}`.
170+
171+
#### Default template variables
172+
173+
- `file_name`: The name of the file being processed.
174+
- `file_directory`: The name of the directory of file that is being processed.
175+
176+
#### Custom Jinja2 filters
177+
178+
##### `latest_release`
179+
180+
This filter fetches the latest release version of a GitHub repository. It takes the repository name as an argument.
181+
182+
```yaml
183+
structure:
184+
- README.md:
185+
content: |
186+
# MyProject
187+
Latest release: {{@ "httpdss/struct" | latest_release @}}
188+
```
189+
190+
This uses PyGithub to fetch the latest release of the repository so setting the `GITHUB_TOKEN` environment variable will give you access to private repositories.
191+
192+
If there is an error in the process, the filter will return `LATEST_RELEASE_ERROR`.
193+
163194
## 👩‍💻 Development
164195

165196
To get started with development, follow these steps:

contribs/generic-app.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,39 @@ structure:
4747
- .gitkeep:
4848
content: ""
4949
- .gitmodules:
50+
content: |
51+
provider "aws" {
52+
region = "{{@ aws_region @}}"
53+
}
54+
- .devops/apps/environments/prod/main.tf:
55+
content: "# Terraform configuration for the prod environment"
56+
- .devops/apps/environments/prod/providers.tf:
57+
content: |
58+
provider "aws" {
59+
region = "{{@ aws_region @}}"
60+
}
61+
- .devops/apps/environments/stage/main.tf:
62+
content: "# Terraform configuration for the stage environment"
63+
- .devops/apps/environments/stage/providers.tf:
64+
content: |
65+
provider "aws" {
66+
region = "{{@ aws_region @}}"
67+
}
68+
- .devops/apps/environments/qa/main.tf:
69+
content: "# Terraform configuration for the qa environment"
70+
- .devops/apps/environments/qa/providers.tf:
71+
content: |
72+
provider "aws" {
73+
region = "{{@ aws_region @}}"
74+
}
75+
- .devops/apps/init/main.tf:
76+
content: "# Terraform configuration for the init environment"
77+
- .devops/apps/init/providers.tf:
78+
content: |
79+
provider "aws" {
80+
region = "{{@ aws_region @}}"
81+
}
82+
- .github/workflows/pre-commit.yaml:
5083
content: |
5184
# This file defines the submodules used by the repository
5285
# See https://git-scm.com/docs/gitmodules

example/structure.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
structure:
22
- README.md:
33
content: |
4-
# ${project_name}
4+
# {{@ project_name @}}
55
This is a template repository.
66
- script.sh:
77
permissions: '0777'
88
content: |
99
#!/bin/bash
10-
echo "Hello, ${author_name}!"
10+
echo "Hello, {{@ author_name @}}!"
1111
- LICENSE:
1212
file: https://raw.githubusercontent.com/nishanths/license/master/LICENSE
1313
- .github/workflows/ci.yml:

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ PyYAML
22
requests
33
openai
44
python-dotenv
5+
jinja2
6+
PyGithub

struct_module/file_item.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
import os
33
import shutil
44
import logging
5-
from string import Template
5+
from jinja2 import Environment
66
import time
77
from openai import OpenAI
88
from dotenv import load_dotenv
9+
from struct_module.filters import get_latest_release
910

1011
load_dotenv()
1112

@@ -17,6 +18,7 @@ class FileItem:
1718
def __init__(self, properties):
1819
self.logger = logging.getLogger(__name__)
1920
self.name = properties.get("name")
21+
self.file_directory = self._get_file_directory()
2022
self.content = properties.get("content")
2123
self.remote_location = properties.get("file")
2224
self.permissions = properties.get("permissions")
@@ -34,6 +36,9 @@ def __init__(self, properties):
3436
self.logger.debug(f"Using OpenAI model: {openai_model}")
3537
self.openai_model = openai_model
3638

39+
def _get_file_directory(self):
40+
return os.path.dirname(self.name)
41+
3742
def process_prompt(self, dry_run=False):
3843
if self.user_prompt:
3944
self.logger.debug(f"Using user prompt: {self.user_prompt}")
@@ -72,11 +77,32 @@ def fetch_content(self):
7277
self.content = response.text
7378
self.logger.debug(f"Fetched content: {self.content}")
7479

80+
def _merge_default_template_vars(self, template_vars):
81+
default_vars = {
82+
"file_name": self.name,
83+
"file_directory": self.file_directory,
84+
}
85+
if not template_vars:
86+
return default_vars
87+
return {**default_vars, **template_vars}
88+
7589
def apply_template_variables(self, template_vars):
76-
if self.content and template_vars:
77-
self.logger.debug(f"Applying template variables: {template_vars}")
78-
template = Template(self.content)
79-
self.content = template.substitute(template_vars)
90+
vars = self._merge_default_template_vars(template_vars)
91+
logging.debug(f"Applying template variables: {vars}")
92+
93+
env = Environment(
94+
trim_blocks=True,
95+
block_start_string='{%@',
96+
block_end_string='@%}',
97+
variable_start_string='{{@',
98+
variable_end_string='@}}',
99+
comment_start_string='{#@',
100+
comment_end_string='@#}'
101+
)
102+
env.filters['latest_release'] = get_latest_release
103+
template = env.from_string(self.content)
104+
105+
self.content = template.render(vars)
80106

81107
def create(self, base_path, dry_run=False, backup_path=None, file_strategy='overwrite'):
82108
file_path = os.path.join(base_path, self.name)

struct_module/filters.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import os
2+
from github import Github
3+
4+
def get_latest_release(repo_name):
5+
token = os.getenv('GITHUB_TOKEN')
6+
7+
# Use the token if available, otherwise proceed without authentication
8+
if token:
9+
g = Github(token)
10+
else:
11+
g = Github()
12+
13+
try:
14+
# Get the repository object
15+
repo = g.get_repo(repo_name)
16+
# Get the latest release
17+
latest_release = repo.get_latest_release()
18+
return latest_release.tag_name
19+
except Exception:
20+
# If an error occurs, return the default branch name
21+
try:
22+
default_branch = repo.default_branch
23+
return default_branch
24+
except Exception as e:
25+
return "LATEST_RELEASE_ERROR"

0 commit comments

Comments
 (0)