batstencil 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- batstencil/__init__.py +5 -0
- batstencil/__main__.py +4 -0
- batstencil/cli/__init__.py +0 -0
- batstencil/cli/bat_stencil/__init__.py +0 -0
- batstencil/cli/bat_stencil/_runner.py +88 -0
- batstencil/cli/bat_stencil/main.py +80 -0
- batstencil/context/__init__.py +4 -0
- batstencil/context/_allconf.py +40 -0
- batstencil/context/_dict.py +25 -0
- batstencil/context/_kwarg.py +10 -0
- batstencil/jinjaext/__init__.py +0 -0
- batstencil/jinjaext/extensions/__init__.py +4 -0
- batstencil/jinjaext/extensions/_embed.py +54 -0
- batstencil/jinjaext/extensions/_repeat_for.py +44 -0
- batstencil/jinjaext/extensions/_setfilename.py +38 -0
- batstencil/jinjaext/extensions/_skip_if.py +24 -0
- batstencil/jinjaext/filters/__init__.py +1 -0
- batstencil/jinjaext/filters/_base64.py +13 -0
- batstencil/renderer/__init__.py +6 -0
- batstencil/renderer/_base.py +283 -0
- batstencil/renderer/_dir.py +253 -0
- batstencil/renderer/_file.py +72 -0
- batstencil/renderer/_stdout.py +14 -0
- batstencil/renderer/_string.py +22 -0
- batstencil/shortcuts/__init__.py +1 -0
- batstencil/shortcuts/_render_stencil.py +19 -0
- batstencil/stencils.py +6 -0
- batstencil/structs/__init__.py +4 -0
- batstencil/structs/_aliases.py +7 -0
- batstencil/structs/_base.py +2 -0
- batstencil/structs/_errors.py +95 -0
- batstencil/structs/interfaces/__init__.py +3 -0
- batstencil/structs/interfaces/_context.py +24 -0
- batstencil/structs/interfaces/_renderer.py +61 -0
- batstencil/structs/interfaces/_template.py +24 -0
- batstencil/template/__init__.py +5 -0
- batstencil/template/_base.py +16 -0
- batstencil/template/_dir.py +122 -0
- batstencil/template/_file.py +53 -0
- batstencil/template/_string.py +16 -0
- batstencil/utils/__init__.py +1 -0
- batstencil/utils/_guessers.py +63 -0
- batstencil-1.0.0.dist-info/METADATA +406 -0
- batstencil-1.0.0.dist-info/RECORD +48 -0
- batstencil-1.0.0.dist-info/WHEEL +5 -0
- batstencil-1.0.0.dist-info/entry_points.txt +2 -0
- batstencil-1.0.0.dist-info/licenses/LICENSE +22 -0
- batstencil-1.0.0.dist-info/top_level.txt +1 -0
batstencil/__init__.py
ADDED
batstencil/__main__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'StencilRunner',
|
|
3
|
+
]
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from batstencil.structs import *
|
|
7
|
+
from batstencil.stencils import *
|
|
8
|
+
import logging
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StencilRunner:
|
|
13
|
+
"""Runs the `bat-stencil` command line (also usable from code)"""
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.verbose: bool = False
|
|
16
|
+
|
|
17
|
+
self.template: Optional[str] = None
|
|
18
|
+
self.string_template: Optional[str] = None
|
|
19
|
+
self.directory_template: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
self.input: Optional[str] = None
|
|
22
|
+
self.additional_arg_list: List[str] = []
|
|
23
|
+
|
|
24
|
+
self.output: Optional[str] = None
|
|
25
|
+
self.no_overwrite: bool = False
|
|
26
|
+
self.no_purge: bool = False
|
|
27
|
+
|
|
28
|
+
def _make_cwd_importable(self):
|
|
29
|
+
cwd = os.getcwd()
|
|
30
|
+
log.debug(f'os.getcwd()={cwd}')
|
|
31
|
+
if cwd not in sys.path:
|
|
32
|
+
sys.path.append(cwd)
|
|
33
|
+
|
|
34
|
+
def run(self):
|
|
35
|
+
self._make_cwd_importable()
|
|
36
|
+
rnd = self.get_renderer()
|
|
37
|
+
rnd.template = self.get_template()
|
|
38
|
+
rnd.context = self.get_context()
|
|
39
|
+
res = rnd.render()
|
|
40
|
+
if self.output:
|
|
41
|
+
if isinstance(res, list):
|
|
42
|
+
for f in res:
|
|
43
|
+
print(f'Wrote file: {f}')
|
|
44
|
+
elif res is None:
|
|
45
|
+
print('Skipped rendering (skip_if)')
|
|
46
|
+
else:
|
|
47
|
+
print(f'Wrote file: {res}')
|
|
48
|
+
|
|
49
|
+
def get_template(self) -> ITemplate:
|
|
50
|
+
if self.directory_template:
|
|
51
|
+
return DirectoryTemplate(self.directory_template)
|
|
52
|
+
elif self.template:
|
|
53
|
+
return FileTemplate(self.template)
|
|
54
|
+
else:
|
|
55
|
+
return StringTemplate(self.string_template)
|
|
56
|
+
|
|
57
|
+
def _parse_arg(self, arg_str: str) -> Tuple[str, str]:
|
|
58
|
+
log.debug(f'_parse_arg({arg_str})')
|
|
59
|
+
if '=' not in arg_str:
|
|
60
|
+
raise ContextError(f'Invalid argument "{arg_str}", expected key=value (e.g. -a foo=bar)')
|
|
61
|
+
key, value = arg_str.split('=', maxsplit=1)
|
|
62
|
+
if not key.strip():
|
|
63
|
+
raise ContextError(f'Invalid argument "{arg_str}", the key is empty')
|
|
64
|
+
return key.strip(), value
|
|
65
|
+
|
|
66
|
+
def get_context(self) -> IContext:
|
|
67
|
+
if self.input:
|
|
68
|
+
ctx = AllConfContext(self.input)
|
|
69
|
+
else:
|
|
70
|
+
ctx = DictContext()
|
|
71
|
+
|
|
72
|
+
if self.additional_arg_list:
|
|
73
|
+
for arg in self.additional_arg_list:
|
|
74
|
+
ctx.nested_update(*self._parse_arg(arg))
|
|
75
|
+
return ctx
|
|
76
|
+
|
|
77
|
+
def get_renderer(self) -> IRenderer:
|
|
78
|
+
if self.directory_template:
|
|
79
|
+
if not self.output:
|
|
80
|
+
raise RenderError('Must specify output directory for directory rendering')
|
|
81
|
+
return DirectoryRenderer(output_path=self.output,
|
|
82
|
+
overwrite=not self.no_overwrite,
|
|
83
|
+
purge=not self.no_purge)
|
|
84
|
+
elif self.output:
|
|
85
|
+
return FileRenderer(self.output,
|
|
86
|
+
overwrite=not self.no_overwrite)
|
|
87
|
+
else:
|
|
88
|
+
return StdOutRenderer()
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from batstencil import __version__ as version
|
|
4
|
+
from batstencil.structs import *
|
|
5
|
+
from ._runner import StencilRunner
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
parser = argparse.ArgumentParser(prog='bat-stencil',
|
|
12
|
+
description='Renders a template (or a directory of templates) using Jinja2'
|
|
13
|
+
' with an AllConf (YAML/JSON) input context',
|
|
14
|
+
epilog=f'bat-stencil version {version}')
|
|
15
|
+
|
|
16
|
+
parser.add_argument('--version', action='version', version=f'%(prog)s {version}')
|
|
17
|
+
|
|
18
|
+
parser.add_argument('-v', '--verbose', action="store_true", help='Spits out DEBUG level logs')
|
|
19
|
+
|
|
20
|
+
parser.add_argument('-i', '--input', nargs='?', help='AllConf file with input context (YAML or JSON)')
|
|
21
|
+
|
|
22
|
+
parser.add_argument('-a', '--arg', action='append',
|
|
23
|
+
help='Add additional Context arguments from the command line, e.g. -a foo=bar'
|
|
24
|
+
' (nested keys with dots, e.g. -a app.name=bar)')
|
|
25
|
+
|
|
26
|
+
parser.add_argument('-o', '--output', help='Path or file to write the results to (otherwise its just printed to stdout).'
|
|
27
|
+
' If this is a path (ends with /), the name of the rendered file will be the same as the input template.',
|
|
28
|
+
default='', nargs='?')
|
|
29
|
+
parser.add_argument('--no-overwrite', action="store_true", help='Makes sure existing output files are not overwritten')
|
|
30
|
+
parser.add_argument('--no-purge', action="store_true", help='Skips purging the output path when rendering directories')
|
|
31
|
+
|
|
32
|
+
input_group = parser.add_mutually_exclusive_group(required=True)
|
|
33
|
+
input_group.add_argument('-t', '--template', default='',
|
|
34
|
+
help='Template file to render')
|
|
35
|
+
input_group.add_argument('-s', '--string-template', default='',
|
|
36
|
+
help='Supply a string directly from the command line to use as a template instead of a file')
|
|
37
|
+
input_group.add_argument('-d', '--directory-template', default='',
|
|
38
|
+
help='Root directory of files and directories to render. This requires the output argument to be a directory')
|
|
39
|
+
|
|
40
|
+
args = parser.parse_args()
|
|
41
|
+
|
|
42
|
+
if args.verbose:
|
|
43
|
+
import logging
|
|
44
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
45
|
+
|
|
46
|
+
log = logging.getLogger(__name__)
|
|
47
|
+
log.info('Verbose logging enabled')
|
|
48
|
+
log.info(f'Args: {args=}')
|
|
49
|
+
|
|
50
|
+
runner = StencilRunner()
|
|
51
|
+
runner.verbose = args.verbose or False
|
|
52
|
+
runner.input = args.input or None
|
|
53
|
+
runner.output = args.output or None
|
|
54
|
+
runner.no_overwrite = args.no_overwrite or False
|
|
55
|
+
runner.no_purge = args.no_purge or False
|
|
56
|
+
runner.template = args.template or None
|
|
57
|
+
runner.string_template = args.string_template or None
|
|
58
|
+
runner.directory_template = args.directory_template or None
|
|
59
|
+
if args.arg:
|
|
60
|
+
for arg in args.arg:
|
|
61
|
+
runner.additional_arg_list.append(arg)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
runner.run()
|
|
65
|
+
except StencilError as e:
|
|
66
|
+
print(f'An error occurred: {e}', file=sys.stderr)
|
|
67
|
+
if e.__cause__ is not None and args.verbose:
|
|
68
|
+
raise
|
|
69
|
+
sys.exit(3)
|
|
70
|
+
except jinja2.TemplateError as e:
|
|
71
|
+
where = f' in {e.filename}' if getattr(e, 'filename', None) else ''
|
|
72
|
+
line = f' (line {e.lineno})' if getattr(e, 'lineno', None) else ''
|
|
73
|
+
print(f'Template error{where}{line}: {e.__class__.__name__}: {e}', file=sys.stderr)
|
|
74
|
+
if args.verbose:
|
|
75
|
+
raise
|
|
76
|
+
sys.exit(3)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == '__main__':
|
|
80
|
+
main()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'AllConfContext',
|
|
3
|
+
'AlvissContext',
|
|
4
|
+
]
|
|
5
|
+
|
|
6
|
+
from batstencil.structs import *
|
|
7
|
+
from allconf import quickloader
|
|
8
|
+
from batutils.tpu import iters
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import logging
|
|
11
|
+
log = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AllConfContext(IContext):
|
|
15
|
+
"""A context loaded from an AllConf (JSON/YAML) file, with all of AllConf's
|
|
16
|
+
bells and whistles (includes, extensions, internal references,
|
|
17
|
+
`${__ENV__:...}`, `${__PY__:...}` etc.)."""
|
|
18
|
+
def __init__(self, file_name: Union[str, Path], **kwargs):
|
|
19
|
+
self._file_name = str(file_name)
|
|
20
|
+
self._data = quickloader.autoload(self._file_name)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def file_name(self) -> str:
|
|
24
|
+
return self._file_name
|
|
25
|
+
|
|
26
|
+
def update(self, key: str, value: Any):
|
|
27
|
+
self.as_dict()[key] = value
|
|
28
|
+
|
|
29
|
+
def nested_update(self, key_tuple: Union[str, Sequence[str]], value: Any):
|
|
30
|
+
log.debug(f'nested_update({key_tuple=}, {value=})')
|
|
31
|
+
if isinstance(key_tuple, str):
|
|
32
|
+
key_tuple = key_tuple.split('.')
|
|
33
|
+
iters.nested_set(self.as_dict(), list(key_tuple), value)
|
|
34
|
+
|
|
35
|
+
def as_dict(self) -> Dict:
|
|
36
|
+
return self._data.as_dict(unmaksed=True)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
AlvissContext = AllConfContext
|
|
40
|
+
"""Deprecated alias of `AllConfContext` (from back when AllConf was Alviss)"""
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'DictContext',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
from batstencil.structs import *
|
|
6
|
+
from batutils.tpu import iters
|
|
7
|
+
import logging
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DictContext(IContext):
|
|
12
|
+
def __init__(self, context_map: Optional[Dict[str, Any]] = None, **kwargs):
|
|
13
|
+
self._context_map = context_map if context_map is not None else {}
|
|
14
|
+
|
|
15
|
+
def update(self, key: str, value: Any):
|
|
16
|
+
self._context_map[key] = value
|
|
17
|
+
|
|
18
|
+
def nested_update(self, key_tuple: Union[str, Sequence[str]], value: Any):
|
|
19
|
+
log.debug(f'nested_update({key_tuple=}, {value=})')
|
|
20
|
+
if isinstance(key_tuple, str):
|
|
21
|
+
key_tuple = key_tuple.split('.')
|
|
22
|
+
iters.nested_set(self._context_map, list(key_tuple), value)
|
|
23
|
+
|
|
24
|
+
def as_dict(self) -> Dict:
|
|
25
|
+
return self._context_map
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'EmbedExtension',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
from jinja2 import nodes
|
|
6
|
+
from jinja2.ext import Extension
|
|
7
|
+
from batstencil.structs import *
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EmbedExtension(Extension):
|
|
11
|
+
"""Adds the `{% embed 'file' %}` tag that embeds a file "as-is" (not as a
|
|
12
|
+
template), with these optional arguments:
|
|
13
|
+
|
|
14
|
+
- `indent=N` to indent every line by N spaces (e.g. for YAML)
|
|
15
|
+
- `allconf=True` to render the file with AllConf first (`alviss=True` is a
|
|
16
|
+
deprecated alias), along with `env=True`, `fidelius=True` and `py=True`
|
|
17
|
+
to resolve `${__ENV__:...}`, `${__FID__:...}` and `${__PY__:...}`
|
|
18
|
+
expressions
|
|
19
|
+
"""
|
|
20
|
+
tags = {'embed'}
|
|
21
|
+
|
|
22
|
+
def __init__(self, environment):
|
|
23
|
+
super().__init__(environment)
|
|
24
|
+
environment.extend(stencil_renderer=None)
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def renderer(self) -> IRenderer:
|
|
28
|
+
return self.environment.stencil_renderer # noqa
|
|
29
|
+
|
|
30
|
+
def parse(self, parser):
|
|
31
|
+
lineno = next(parser.stream).lineno
|
|
32
|
+
args = [parser.parse_expression()]
|
|
33
|
+
source_file = parser.filename
|
|
34
|
+
kwargs = [nodes.Keyword('source_file', nodes.Const(source_file))]
|
|
35
|
+
while parser.stream.skip_if('comma'):
|
|
36
|
+
key = parser.stream.expect('name').value
|
|
37
|
+
parser.stream.expect('assign')
|
|
38
|
+
value = parser.parse_expression()
|
|
39
|
+
kwargs.append(nodes.Keyword(key, value))
|
|
40
|
+
|
|
41
|
+
return nodes.CallBlock(self.call_method('_render_embed', args, kwargs), [], [], []).set_lineno(lineno)
|
|
42
|
+
|
|
43
|
+
def _render_embed(self, file_path, source_file: Optional[str] = None, indent: int = 0,
|
|
44
|
+
caller=None, **kwargs):
|
|
45
|
+
|
|
46
|
+
# Check if file_path is a variable in the context
|
|
47
|
+
if callable(file_path):
|
|
48
|
+
file_path = file_path()
|
|
49
|
+
|
|
50
|
+
content = self.renderer.get_embed(file_path, source_file=source_file, **kwargs)
|
|
51
|
+
|
|
52
|
+
indent_str = ' ' * int(indent or 0)
|
|
53
|
+
|
|
54
|
+
return f'{indent_str}{indent_str.join(content.splitlines(True))}\n'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'RepeatForExtension',
|
|
3
|
+
]
|
|
4
|
+
from jinja2 import nodes
|
|
5
|
+
from jinja2.ext import Extension
|
|
6
|
+
from batstencil.structs import *
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RepeatForExtension(Extension):
|
|
10
|
+
"""Adds the `{% repeat_for item in items %}` tag (also
|
|
11
|
+
`{% repeat_for key, value in some_dict.items() %}`) that makes the
|
|
12
|
+
`DirectoryRenderer` render the template, or the directory when used in a
|
|
13
|
+
`__stencil_meta__` file, once per item, with the loop variable(s) added to
|
|
14
|
+
the context (and loop info in `__stencil__.loop`).
|
|
15
|
+
|
|
16
|
+
Use `{% setfilename %}` along with it to give each copy a unique name.
|
|
17
|
+
"""
|
|
18
|
+
tags = {'repeat_for'}
|
|
19
|
+
|
|
20
|
+
def __init__(self, environment):
|
|
21
|
+
super().__init__(environment)
|
|
22
|
+
environment.extend(stencil_renderer=None)
|
|
23
|
+
|
|
24
|
+
def parse(self, parser):
|
|
25
|
+
lineno = next(parser.stream).lineno
|
|
26
|
+
target = parser.parse_assign_target(extra_end_rules=('name:in',))
|
|
27
|
+
if isinstance(target, nodes.Name):
|
|
28
|
+
names = [target.name]
|
|
29
|
+
elif isinstance(target, nodes.Tuple) and all(isinstance(n, nodes.Name) for n in target.items):
|
|
30
|
+
names = [n.name for n in target.items]
|
|
31
|
+
else:
|
|
32
|
+
parser.fail('repeat_for expects a variable name (or names) like: repeat_for item in items', lineno)
|
|
33
|
+
parser.stream.expect('name:in')
|
|
34
|
+
iterable = parser.parse_expression()
|
|
35
|
+
return nodes.CallBlock(self.call_method('_repeat_for', [nodes.Const(names), iterable]), # noqa
|
|
36
|
+
[], [], []).set_lineno(lineno)
|
|
37
|
+
|
|
38
|
+
def _repeat_for(self, names: List[str], iterable, caller):
|
|
39
|
+
renderer = self.environment.stencil_renderer # noqa
|
|
40
|
+
if getattr(renderer, 'repeat_mode', None) == 'ignore':
|
|
41
|
+
return '' # The loop item has already been set
|
|
42
|
+
if isinstance(iterable, jinja2.Undefined) or iterable is None:
|
|
43
|
+
iterable = []
|
|
44
|
+
raise RepeatRendering(names, iterable)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'SetFilenameExtension',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
from jinja2 import nodes
|
|
6
|
+
from jinja2.ext import Extension
|
|
7
|
+
from batstencil.structs import *
|
|
8
|
+
import logging
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SetFilenameExtension(Extension):
|
|
13
|
+
"""Adds the `{% setfilename %}...{% endsetfilename %}` tag that sets the
|
|
14
|
+
name of the rendered file (or directory, in `__stencil_meta__` files) to
|
|
15
|
+
whatever its body renders as (stripped)."""
|
|
16
|
+
tags = {'setfilename'}
|
|
17
|
+
|
|
18
|
+
def __init__(self, environment):
|
|
19
|
+
super().__init__(environment)
|
|
20
|
+
environment.extend(stencil_renderer=None)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def renderer(self) -> IRenderer:
|
|
24
|
+
return self.environment.stencil_renderer # noqa
|
|
25
|
+
|
|
26
|
+
def parse(self, parser):
|
|
27
|
+
lineno = next(parser.stream).lineno
|
|
28
|
+
body = parser.parse_statements(('name:endsetfilename',), drop_needle=True)
|
|
29
|
+
return nodes.CallBlock(self.call_method('_set_filename', [nodes.Const(parser.filename)]),
|
|
30
|
+
[], [], body).set_lineno(lineno)
|
|
31
|
+
|
|
32
|
+
def _set_filename(self, source_file: Optional[str], caller):
|
|
33
|
+
name = caller().strip()
|
|
34
|
+
if not name:
|
|
35
|
+
log.warning(f'The setfilename tag in {source_file or "a template"} rendered an empty file name'
|
|
36
|
+
f' (check the template code and context), so it is ignored and the default name is used')
|
|
37
|
+
self.renderer.output_file_name = name
|
|
38
|
+
return ''
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'SkipIfExtension',
|
|
3
|
+
]
|
|
4
|
+
from jinja2 import nodes
|
|
5
|
+
from jinja2.ext import Extension
|
|
6
|
+
from batstencil.structs import *
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SkipIfExtension(Extension):
|
|
10
|
+
"""Adds the `{% skip_if <condition> %}` tag that skips rendering the
|
|
11
|
+
template (or directory, in `__stencil_meta__` files) if the condition is
|
|
12
|
+
true."""
|
|
13
|
+
tags = {'skip_if'}
|
|
14
|
+
|
|
15
|
+
def parse(self, parser):
|
|
16
|
+
lineno = next(parser.stream).lineno
|
|
17
|
+
# Parse the condition expression
|
|
18
|
+
condition = parser.parse_expression()
|
|
19
|
+
return nodes.CallBlock(self.call_method('_check_condition', [condition]), [], [], []).set_lineno(lineno)
|
|
20
|
+
|
|
21
|
+
def _check_condition(self, condition, caller):
|
|
22
|
+
if condition:
|
|
23
|
+
raise CancelRendering('Rendering cancelled due to a skip_if condition being true')
|
|
24
|
+
return ''
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from ._base64 import *
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'base64',
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
import base64 as _base64
|
|
6
|
+
from typing import *
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def base64(value: Union[str, bytes]) -> str:
|
|
10
|
+
"""Base64 encodes a value (e.g. for Kubernetes Secrets) as a single line"""
|
|
11
|
+
if not isinstance(value, bytes):
|
|
12
|
+
value = str(value).encode('utf-8')
|
|
13
|
+
return _base64.b64encode(value).decode('ascii')
|