Skip to content

Commit

Permalink
Merge pull request #4 from ccpgames/feature/meta-tag-to-cancel-rendering
Browse files Browse the repository at this point in the history
Feature/meta tag to cancel rendering
  • Loading branch information
CCP-Zeulix authored May 30, 2024
2 parents 057e7f2 + 15f6490 commit c03cbdb
Show file tree
Hide file tree
Showing 16 changed files with 144 additions and 14 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.0] - 2024-05-30

### Added

- New Extension that adds a `{% skip_if %}` tag where you can insert any
condition (e.g. checking the Context) and if it evals as True, a
`CancelRendering` exception is raised and caught by the Renderers causing
them to silently just skip rendering that template.
- The `StringRenderer` returns `None`
- The `StoutRenderer` prints out nothing
- The `FileRenderer` doesn't create an output file
- Added unittests for the `{% skip_if %}` tag


## [0.3.0] - 2024-05-28

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion ccpstencil/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '0.3.0'
__version__ = '0.4.0'

__author__ = 'Thordur Matthiasson <[email protected]>'
__license__ = 'MIT License'
Expand Down
1 change: 1 addition & 0 deletions ccpstencil/jinjaext/extensions/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from ._embed import *
from ._skip_if import *
21 changes: 21 additions & 0 deletions ccpstencil/jinjaext/extensions/_skip_if.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
__all__ = [
'SkipIfExtension',
]
from jinja2 import nodes
from jinja2.ext import Extension
from ccpstencil.structs import *


class SkipIfExtension(Extension):
tags = {'skip_if'}

def parse(self, parser):
lineno = next(parser.stream).lineno
# Parse the condition expression
condition = parser.parse_expression()
return nodes.CallBlock(self.call_method('_check_condition', [condition]), [], [], []).set_lineno(lineno)

def _check_condition(self, condition, caller):
if condition:
raise CancelRendering("Rendering cancelled due to meta_only_render_if condition being False")
return ''
19 changes: 14 additions & 5 deletions ccpstencil/renderer/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,22 @@ def template(self, value: ITemplate):
value.set_renderer(self)
self._template = value

@abc.abstractmethod
def render(self):
"""This should just be called by subclasses via super().render() in
order to run preflight and common stuff, but the empty return value
should be ignored.
"""
self._pre_flight()
try:
rendered_string = self._render_as_string()
return self._output_rendered_results(rendered_string)
except CancelRendering:
log.info(f'Rendering cancelled by skip_if tag')
return None

@abc.abstractmethod
def _output_rendered_results(self, rendered_string: str):
pass

@abc.abstractmethod
def _render_as_string(self) -> str:
pass

@property
def jinja_environment(self) -> jinja2.Environment:
Expand Down
14 changes: 12 additions & 2 deletions ccpstencil/renderer/_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,19 @@ def __init__(self, output_file: Union[str, Path],
super().__init__(context, template, **kwargs)

def render(self) -> str:
return super().render()

def _output_rendered_results(self, rendered_string: str) -> str:
results = super().render()
if results is None:
return f'Skipped: {self._output_file.absolute()}'

if self._output_file.exists() and not self._overwrite:
raise OutputFileExistsError(f'The target output file already exists and overwriting is disabled: {self._output_file.absolute()}')
raise OutputFileExistsError(f'The target output file already exists and overwriting is'
f' disabled: {self._output_file.absolute()}')
self._output_file.parent.mkdir(parents=True, exist_ok=True)

with open(self._output_file, 'w') as fout:
fout.write(super().render())
fout.write(results)
return str(self._output_file.absolute())

8 changes: 5 additions & 3 deletions ccpstencil/renderer/_stdout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
'StdOutRenderer',
]


from ccpstencil.structs import *
from ._string import *


class StdOutRenderer(StringRenderer):
def render(self):
print(super().render())
def render(self) -> NoReturn:
results = super().render()
if results is not None:
print(results)
9 changes: 6 additions & 3 deletions ccpstencil/renderer/_string.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ class StringRenderer(_BaseRenderer):
def __init__(self, context: Optional[IContext] = None, template: Optional[ITemplate] = None, **kwargs):
super().__init__(context, template, **kwargs)

def render(self) -> str:
super().render()
return self.template.get_jinja_template().render(**self.context.as_dict())
def render(self) -> Optional[str]:
return super().render()

def _render_as_string(self) -> str:
return self.template.get_jinja_template().render(**self.context.as_dict())

def _output_rendered_results(self, rendered_string: str) -> str:
return rendered_string
6 changes: 6 additions & 0 deletions ccpstencil/structs/_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
'InvalidTemplateTypeForRendererError',
'OutputFileExistsError',
'EmbedFileNotFound',

'CancelRendering',
]


Expand Down Expand Up @@ -53,3 +55,7 @@ class OutputFileExistsError(RenderError, FileExistsError):

class EmbedFileNotFound(RenderError, FileNotFoundError):
pass


class CancelRendering(Exception):
pass
10 changes: 10 additions & 0 deletions tests/res/skipif/context.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
mode:
normal:
adjective: Now
positive:
enabled: true
adjective: Shiny
negative:
enabled: false
adjective: Shitty
number: 7
2 changes: 2 additions & 0 deletions tests/res/skipif/template_negative.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{% skip_if not mode.negative.enabled %}
The day is {{mode.negative.adjective}}
1 change: 1 addition & 0 deletions tests/res/skipif/template_normal.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The day is {{mode.normal.adjective}}
2 changes: 2 additions & 0 deletions tests/res/skipif/template_positive.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{% skip_if not mode.positive.enabled %}
The day is {{mode.positive.adjective}}
2 changes: 2 additions & 0 deletions tests/res/skipif/template_seven.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{% skip_if number == 7 %}
Seven!
2 changes: 2 additions & 0 deletions tests/res/skipif/template_six.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{% skip_if number == 6 %}
Six!
45 changes: 45 additions & 0 deletions tests/test_skip_if.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import unittest

from ccpstencil.stencils import *

import os
import pathlib

import logging
logging.basicConfig(level=logging.DEBUG)


_HERE = pathlib.Path(__file__).parent.resolve()


def fp(file_name: str) -> str:
return str((_HERE / 'res/skipif/' / file_name).absolute())


class TestSkipIf(unittest.TestCase):
@classmethod
def setUpClass(cls):
os.environ['STENCIL_TEMPLATE_PATH'] = str(_HERE / 'res/skipif/')

def test_normal(self):
res = render_stencil('template_normal.txt', fp('context.yaml'))
self.assertEqual('The day is Now', res)

def test_skipped(self):
res = render_stencil('template_negative.txt', fp('context.yaml'))
self.assertIsNone(res)

def test_not_skipped(self):
res = render_stencil('template_positive.txt', fp('context.yaml'))
self.assertEqual('The day is Shiny', res)

def test_numbers(self):
# res = render_stencil('template_six.txt', fp('context.yaml'))
# self.assertEqual('Six!', res)
res = render_stencil('template_seven.txt', fp('context.yaml'))
self.assertIsNone(res)

@classmethod
def tearDownClass(cls):
if 'STENCIL_TEMPLATE_PATH' in os.environ:
del os.environ['STENCIL_TEMPLATE_PATH']

0 comments on commit c03cbdb

Please sign in to comment.