Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: bump version to 1.4.1 #293

Merged
merged 11 commits into from
Nov 13, 2023
Merged
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM fedora as manpage_builder
FROM fedora:38 as manpage_builder
RUN dnf install -y make pandoc python3.11-pip
WORKDIR /app
RUN pip install poetry
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ generate-man:
@export QPC_VAR_CURRENT_YEAR=$(shell date +'%Y') \
&& export QPC_VAR_PROJECT=$${QPC_VAR_PROJECT:-Quipucords} \
&& export QPC_VAR_PROGRAM_NAME=$${QPC_VAR_PROGRAM_NAME:-qpc} \
&& poetry run jinja -X QPC_VAR docs/source/man.j2 $(ARGS)
&& poetry run python docs/jinja-render.py -e '^QPC_VAR.*' -t docs/source/man.j2 $(ARGS)

update-man.rst:
$(MAKE) generate-man ARGS="-o docs/source/man.rst"
Expand Down
78 changes: 78 additions & 0 deletions docs/jinja-render.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""
Barebones command-line utility to render a Jinja template.

Uses environment variables to populate the template.

Example usage:

# define relevant environment variables
export QPC_VAR_PROGRAM_NAME=qpc
export QPC_VAR_PROJECT=Quipucords
export QPC_VAR_CURRENT_YEAR=$(date +'%Y')

# use stdin to read template and stdout to write output:
python ./jinja-render.py -e '^QPC_VAR_.*' \
< ./source/man.j2 > ./source/man.rst

# use arguments to specify template and output paths:
python ./jinja-render.py -e '^QPC_VAR_.*' \
-t ./source/man.j2 -o ./source/man.rst
"""

import argparse
import os
import re

from jinja2 import DictLoader, Environment


def get_env_vars(allow_pattern):
"""Get the matching environment variables."""
env_vars = {}
re_pattern = re.compile(allow_pattern)
for key, value in os.environ.items():
if re_pattern.search(key):
env_vars[key] = value
return env_vars


def get_template(template_file):
"""Load the Jinja template."""
with template_file as f:
template_data = f.read()
return Environment(
loader=DictLoader({"-": template_data}), keep_trailing_newline=True
).get_template("-")


def get_args():
"""Parse and return command line arguments."""
parser = argparse.ArgumentParser(description="Format Jinja template using env vars")
parser.add_argument(
"-e",
"--env_var_pattern",
type=str,
default="",
help="regex pattern to match environment variable names",
)
parser.add_argument("-o", "--output", type=argparse.FileType("w"), default="-")
parser.add_argument("-t", "--template", type=argparse.FileType("r"), default="-")
args = parser.parse_args()
return args


def main():
"""Parse command line args and render Jinja template to output."""
args = get_args()
template = get_template(template_file=args.template)
env_vars = get_env_vars(allow_pattern=args.env_var_pattern)
args.output.write(template.render(env_vars))
if hasattr(args.output, "name"):
# This is a side effect of how ArgumentParser handles files vs stdout.
# Real output files have a "name" attribute and should be closed.
# However, we do NOT want to close if it's stdout, which has no name.
args.output.close()


if __name__ == "__main__":
main()
67 changes: 38 additions & 29 deletions docs/source/man.j2

Large diffs are not rendered by default.

67 changes: 38 additions & 29 deletions docs/source/man.rst

Large diffs are not rendered by default.

101 changes: 101 additions & 0 deletions docs/test_jina_render.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Tests for jinja-render.py standalone script."""

import importlib.util
import os
import random
import sys
import tempfile
from io import StringIO
from pathlib import Path

import pytest

sample_jinja_template = "hello, {{ NAME }}"


@pytest.fixture(scope="module")
def jinja_render():
"""
Import the jinja-render script as a module.

This is necessary because jinja-render.py is a standalone script
that does not live in a regular Python package.
"""
module_name = "jinja_render"
file_path = Path(__file__).parent / "jinja-render.py"
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module


def test_get_env_vars(jinja_render, mocker):
"""Test getting only env vars that match the given pattern."""
mocker.patch.dict(
os.environ,
{
"unrelated": "zero",
"QPC_THING": "one",
"QPC_thang": "two",
"NOT_QPC_OTHER": "three",
},
clear=True,
)
expected = {"QPC_THING": "one", "QPC_thang": "two"}
allow_pattern = "^QPC_.*"
actual = jinja_render.get_env_vars(allow_pattern)
assert actual == expected


def test_read_stdin_write_stdout(jinja_render, mocker, capsys):
"""Test reading the Jinja template from stdin and writing output to stdout."""
fake_name = str(random.random())
expected_stdout = f"hello, {fake_name}"

fake_env_vars = {"NAME": fake_name}
fake_sys_argv = ["script.py", "-e", ".*"]
fake_stdin = StringIO(sample_jinja_template)

mocker.patch.dict(os.environ, fake_env_vars, clear=True)
mocker.patch.object(sys, "argv", fake_sys_argv)
mocker.patch.object(sys, "stdin", fake_stdin)

jinja_render.main()
actual_stdout = capsys.readouterr().out
assert actual_stdout == expected_stdout


@pytest.fixture
def template_path():
"""Temp file containing a Jija template."""
tmp_file = tempfile.NamedTemporaryFile()
tmp_file.write(sample_jinja_template.encode())
tmp_file.seek(0)
yield tmp_file.name
tmp_file.close()


def test_read_file_write_file(jinja_render, template_path, mocker, capsys):
"""Test reading the Jinja template from file and writing output to file."""
fake_name = str(random.random())
expected_stdout = f"hello, {fake_name}"
fake_env_vars = {"NAME": fake_name}
with tempfile.TemporaryDirectory() as output_directory:
output_path = Path(output_directory) / str(random.random())
fake_sys_argv = [
"script.py",
"-e",
".*",
"-t",
template_path,
"-o",
str(output_path),
]
mocker.patch.dict(os.environ, fake_env_vars, clear=True)
mocker.patch.object(sys, "argv", fake_sys_argv)
jinja_render.main()

with output_path.open() as output_file:
actual_output = output_file.read()

assert actual_output == expected_stdout
Loading