diff --git a/.gitignore b/.gitignore
index 9f431dd..0c9d7be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,3 +35,10 @@ nosetests.xml
.pytest_cache
dist/
+
+.python-version
+*.txt
+.venv
+.vscode
+*.code-workspace
+.idea
diff --git a/Makefile b/Makefile
index 08ebf93..5d63e97 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
test:
- pytest -v
+ tox
publish: clean
python setup.py sdist bdist_wheel
@@ -7,5 +7,6 @@ publish: clean
clean:
rm -rf *.egg-info *.egg dist build .pytest_cache
+ rm -rf .tox/py* .tox/dist/*
.PHONY: test publish clean
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..8ce973e
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+Jinja2
diff --git a/requirements_dev.txt b/requirements_dev.txt
new file mode 100644
index 0000000..27229da
--- /dev/null
+++ b/requirements_dev.txt
@@ -0,0 +1,9 @@
+-e git+https://github.com/mattrobenolt/jinja2-cli.git#egg=jinja2_cli
+pytest
+pytest-xdist
+schema
+toml
+pyyaml
+xmltodict
+json5
+hjson; python_version > '2.7'
diff --git a/tests/common.py b/tests/common.py
new file mode 100644
index 0000000..22bdddb
--- /dev/null
+++ b/tests/common.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""Common helper classes and functions used by test cases
+"""
+
+import subprocess
+import sys
+
+from jinja2 import nodes
+from jinja2.ext import Extension
+
+from jinja2cli import cli
+
+
+class UpperCaseExtension(Extension):
+ """A custom jinja2 extension that adds a new tag `upperrcase`,
+ an upper case block capitalizes all text an variables within
+ the block.
+
+ example:
+ ```template.j2
+ ..
+ {% uppercase %}{{foo}}{% enduppercase %}
+ ..
+ ```
+
+ ```
+ >>> import jinja2
+ >>> from common import UpperCaseExtension
+
+ >>> env = jinja2.Environment(extensions=[UpperCaseExtension])
+ >>> loader = jinja2.FileSystemLoader("path/to/templates/dir")
+ >>> template = loader.load(env, "template.j2")
+ >>> render = template.render(foo="bar")
+ >>> print(render)
+ BAR
+ ```
+ """
+
+ tags = {"uppercase"}
+
+ def parse(self, parser):
+ lineno = next(parser.stream).lineno
+ args = [] # the uppercase tag doesn't require any arguments
+ body = parser.parse_statements(["name:enduppercase"], drop_needle=True)
+ ret = nodes.CallBlock(
+ self.call_method("_uppercase", args), [], [], body
+ ).set_lineno(lineno)
+ return ret
+
+ def _uppercase(self, caller=None):
+ if caller is not None:
+ return caller().upper()
+ return ""
+
+
+class CliOpts(object):
+ """A helper class to mimic jinja2cli.cli options (passed from optparse)"""
+
+ def __init__(self, **opts):
+ self.format = opts.get("format", None)
+ self.D = opts.get("D", None)
+ self.extensions = opts.get("extensions", [])
+ self.section = opts.get("section", None)
+ self.outfile = opts.get("outfile", None)
+ self.strict = opts.get("strict", False)
+
+
+def is_python_version(major, minor=None):
+ if minor is None:
+ return sys.version_info.major == major
+ return sys.version_info[:2] == (major, minor)
+
+
+def to_unicode(string):
+ if is_python_version(3):
+ return bytes(string, "utf-8")
+ else:
+ return unicode(string)
+
+
+def run_jinja2(template, datafile="", args=None, input_data=""):
+ """Run jinja2 with the given template, path to data file, input_data and other options, capture the error and output streams and return them"""
+
+ cmd_args = [template]
+
+ if datafile:
+ cmd_args.append(datafile)
+
+ if args:
+ cmd_args.extend(list(args))
+
+ proc = subprocess.Popen(
+ ["jinja2"] + cmd_args,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+
+ if input_data:
+ out, err = proc.communicate(input=to_unicode(input_data))
+ else:
+ out, err = proc.communicate()
+
+ try:
+ proc.terminate()
+ except OSError:
+ pass
+
+ return cli.force_text(out), cli.force_text(err)
diff --git a/tests/test_data_input_stdin.py b/tests/test_data_input_stdin.py
new file mode 100644
index 0000000..f9cce61
--- /dev/null
+++ b/tests/test_data_input_stdin.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+
+import pytest
+
+from jinja2cli import cli
+
+from .common import run_jinja2
+
+TEMPLATE = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), "files/template.j2"
+)
+
+
+def test_stdin_implicit_subprocess():
+ out, err = run_jinja2(TEMPLATE, input_data="title: foo")
+ assert "" == err
+ assert "foo" == out
+
+
+def test_stdin_explicit_subprocess():
+ out, err = run_jinja2(TEMPLATE, "-", input_data="title: foo")
+ assert "" == err
+ assert "foo" == out
diff --git a/tests/test_format_env.py b/tests/test_format_env.py
new file mode 100644
index 0000000..9514c84
--- /dev/null
+++ b/tests/test_format_env.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from jinja2cli import cli
+
+EnvParserFn, EnvExpectException, EnvRaiseException = cli.get_format("env")
+
+
+@pytest.mark.parametrize(
+ ("env", "env_data"),
+ [
+ ("", {}),
+ ("foo=bar", {"foo": "bar"}),
+ ("foo=", {"foo": ""}),
+ ("foo=bar\nham=spam", {"foo": "bar", "ham": "spam"}),
+ ],
+)
+def test_env(env, env_data):
+ assert EnvParserFn(env) == env_data
+
+
+def test_env_multiline_value():
+ with pytest.raises(EnvExpectException):
+ EnvParserFn("foo=bar\nham\nspam")
diff --git a/tests/test_format_hjson.py b/tests/test_format_hjson.py
new file mode 100644
index 0000000..4972c3d
--- /dev/null
+++ b/tests/test_format_hjson.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from jinja2cli import cli
+from .common import is_python_version
+
+try:
+ HJsonParserFn, HJsonExpectException, HJsonRaiseException = cli.get_format(
+ "hjson"
+ )
+except cli.InvalidDataFormat:
+ HJsonParserFn = lambda x: x
+ HJsonExpectException = BaseException
+ HJsonRaiseException = BaseException
+
+
+HJSON_STR = """{
+ // use #, // or /**/ comments,
+ // omit quotes for keys
+ key: 1
+ // omit quotes for strings
+ contains: everything on this line
+ // omit commas at the end of a line
+ cool: {
+ foo: 1
+ bar: 2
+ }
+ // allow trailing commas
+ list: [
+ 1,
+ 2,
+ ]
+ // and use multiline strings
+ realist:
+ '''
+ My half empty glass,
+ I will fill your empty half.
+ Now you are half full.
+ '''
+}
+"""
+
+EXPECTED_DATA = {
+ "key": 1,
+ "contains": "everything on this line",
+ "cool": {"foo": 1, "bar": 2},
+ "list": [1, 2],
+ "realist": "My half empty glass,\nI will fill your empty half.\nNow you are half full.",
+}
+
+
+@pytest.mark.skipif(
+ is_python_version(2, 7),
+ reason="there's no hjson package compatible with Python2.7",
+)
+@pytest.mark.parametrize(
+ ("hjson", "expected"),
+ [
+ ("", {}),
+ ("{}", {}),
+ (HJSON_STR, EXPECTED_DATA),
+ ],
+)
+def test_hjson(hjson, expected):
+ assert HJsonParserFn(hjson) == expected
diff --git a/tests/test_format_ini.py b/tests/test_format_ini.py
new file mode 100644
index 0000000..c2ac4f5
--- /dev/null
+++ b/tests/test_format_ini.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from jinja2cli import cli
+from .common import is_python_version
+
+IIinParserFn, IniExpectException, IniRaiseException = cli.get_format("ini")
+
+
+@pytest.mark.xfail(
+ is_python_version(3, 12),
+ strict=True,
+ raises=AttributeError,
+ reason="Jinja2cli uses `configparser.ConfigParser.readfp` that is deprecated in Python3.12",
+)
+@pytest.mark.parametrize(
+ ("ini", "ini_data"),
+ [
+ ("", {}),
+ ("[data]\nfoo=bar", {"data": {"foo": "bar"}}),
+ ("[data]\nfoo=bar\nham=spam", {"data": {"foo": "bar", "ham": "spam"}}),
+ (
+ "[data1]\nfoo=bar\n[data2]\nham=spam",
+ {"data1": {"foo": "bar"}, "data2": {"ham": "spam"}},
+ ),
+ pytest.param(
+ "[data]\nfoo=bar\n[data]\nham=spam",
+ {"data": {"foo": "bar", "ham": "spam"}},
+ marks=pytest.mark.skipif(
+ not is_python_version(2, 7),
+ reason="Duplicate data section names are not supported by configparser in python versions > 2.7",
+ ),
+ ),
+ ],
+)
+def test_ini(ini, ini_data):
+ assert IIinParserFn(ini) == ini_data
+
+
+@pytest.mark.xfail(
+ is_python_version(3, 12),
+ strict=True,
+ raises=AttributeError,
+ reason="Jinja2cli uses `configparser.ConfigParser.readfp` that is deprecated in Python3.12",
+)
+@pytest.mark.parametrize(
+ ("data"),
+ [
+ ("foo=bar\nham=spam"),
+ ("[data]foo=\nbar"),
+ ],
+)
+def test_ini_errors(data):
+ with pytest.raises(IniExpectException):
+ IIinParserFn(data)
diff --git a/tests/test_format_json.py b/tests/test_format_json.py
new file mode 100644
index 0000000..dc3d135
--- /dev/null
+++ b/tests/test_format_json.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+
+import pytest
+
+from jinja2cli import cli
+
+JsonParserFn, JsonExpectException, JsonRaiseException = cli.get_format("json")
+
+
+@pytest.mark.parametrize(
+ ("json", "json_data"),
+ [
+ ("{}", {}),
+ ('{"foo": "bar"}', {"foo": "bar"}),
+ ('{"": "bar"}', {"": "bar"}),
+ ('{"foo": "bar", "ham": "spam"}', {"foo": "bar", "ham": "spam"}),
+ ],
+)
+def test_json(json, json_data):
+ assert JsonParserFn(json) == json_data
+
+
+@pytest.mark.parametrize(
+ ("data"),
+ [
+ (""),
+ ('{foo: "bar"}'),
+ ('{"foo": bar}'),
+ ],
+)
+def test_json_errors(data):
+ with pytest.raises(JsonExpectException):
+ JsonParserFn(data)
diff --git a/tests/test_format_json5.py b/tests/test_format_json5.py
new file mode 100644
index 0000000..41f80d8
--- /dev/null
+++ b/tests/test_format_json5.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from jinja2cli import cli
+
+
+Json5ParserFn, Json5ExpectException, Json5RaiseException = cli.get_format("json5")
+
+
+@pytest.mark.parametrize(
+ ("json5", "expected"),
+ [
+ ("{}", {}),
+ (
+ "{ // comments\n "
+ "unquoted: 'and you can quote me on that',\n "
+ "singleQuotes: 'I can use \"double quotes\" here',\n "
+ "hexadecimal: 0xdecaf,\n "
+ "leadingDecimalPoint: .8675309, andTrailing: 8675309.,\n "
+ "positiveSign: +1,\n "
+ "trailingComma: 'in objects', andIn: ['arrays',],\n "
+ "\"backwardsCompatible\": \"with JSON\",\n"
+ "}\n",
+ {
+ "unquoted": "and you can quote me on that",
+ "singleQuotes": 'I can use "double quotes" here',
+ "hexadecimal": 0xDECAF,
+ "leadingDecimalPoint": 0.8675309,
+ "andTrailing": 8675309.0,
+ "positiveSign": 1,
+ "trailingComma": "in objects",
+ "andIn": [
+ "arrays",
+ ],
+ "backwardsCompatible": "with JSON",
+ },
+ ),
+ pytest.param(
+ '{foo: "bar\nham"}',
+ {"foo": "bar\nham"},
+ marks=pytest.mark.skip(
+ reason="json5 package doesn't support line breaks in values, may change in the future since hjson"
+ ),
+ ),
+ ],
+)
+def test_json5(json5, expected):
+ assert Json5ParserFn(json5) == expected
+
+
+@pytest.mark.parametrize(
+ ("json5"),
+ [
+ (""),
+ ],
+)
+def test_json5_errors(json5):
+ with pytest.raises(Json5ExpectException):
+ Json5ParserFn(json5)
diff --git a/tests/test_format_querystring.py b/tests/test_format_querystring.py
new file mode 100644
index 0000000..90a6dbb
--- /dev/null
+++ b/tests/test_format_querystring.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from jinja2cli import cli
+
+QSParserFn, QSExpectException, QSRaiseException = cli.get_format("querystring")
+
+
+@pytest.mark.parametrize(
+ ("qs", "expected"),
+ [
+ ("", {}),
+ ("foo=bar", {"foo": "bar"}),
+ ("foo=", {}),
+ ("foo=bar&ham=spam", {"foo": "bar", "ham": "spam"}),
+ (
+ "foo.bar=ham&ham.spam=eggs",
+ {"foo": {"bar": "ham"}, "ham": {"spam": "eggs"}},
+ ),
+ ("foo=bar%20ham%20spam", {"foo": "bar ham spam"}),
+ ("foo=bar%2Eham%2Espam", {"foo": "bar.ham.spam"}),
+ (
+ "foo.bar.ham=spam&foo.bar.spam=eggs",
+ {"foo": {"bar": {"ham": "spam", "spam": "eggs"}}},
+ ),
+ (
+ "foo.bar.ham=spam&foo.baz.ham=spam",
+ {"foo": {"bar": {"ham": "spam"}, "baz": {"ham": "spam"}}},
+ ),
+ ],
+)
+def test_qs(qs, expected):
+ assert expected == QSParserFn(qs)
diff --git a/tests/test_format_toml.py b/tests/test_format_toml.py
new file mode 100644
index 0000000..7f7d9bc
--- /dev/null
+++ b/tests/test_format_toml.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from jinja2cli import cli
+
+TomlParserFn, TomlExpectException, TomlRaiseException = cli.get_format("toml")
+
+
+@pytest.mark.parametrize(
+ ("toml", "toml_data"),
+ [
+ ("", {}),
+ ("foo=''\n", {"foo": ""}),
+ ("foo='bar'\n", {"foo": "bar"}),
+ ("''='bar'\n", {"": "bar"}),
+ ],
+)
+def test_toml(toml, toml_data):
+ assert TomlParserFn(toml) == toml_data
+
+
+@pytest.mark.parametrize(
+ ("data"),
+ [
+ ("foo"),
+ ("foo=bar\n"),
+ ("foo=bar ham=spam\n"),
+ ("='bar'\n"),
+ ],
+)
+def test_toml_errors(data):
+ with pytest.raises(TomlExpectException):
+ TomlParserFn(data)
diff --git a/tests/test_format_xml.py b/tests/test_format_xml.py
new file mode 100644
index 0000000..c113730
--- /dev/null
+++ b/tests/test_format_xml.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from jinja2cli import cli
+
+XMLParserFn, XMLExpectException, XMLRaiseException = cli.get_format("xml")
+
+
+@pytest.mark.parametrize(
+ ("xml", "xml_data"),
+ [
+ ("", {"data": None}),
+ ("foo", {"data": "foo"}),
+ ("", {"data": {"foo": None}}),
+ ("bar", {"data": {"foo": "bar"}}),
+ (
+ "barspam",
+ {"data": {"foo": "bar", "ham": "spam"}},
+ ),
+ ],
+)
+def test_xml(xml, xml_data):
+ assert XMLParserFn(xml) == xml_data
+
+
+@pytest.mark.parametrize(
+ ("data"),
+ [
+ (""),
+ (""),
+ (""),
+ (""),
+ ],
+)
+def test_xml_errors(data):
+ with pytest.raises(XMLExpectException):
+ XMLParserFn(data)
diff --git a/tests/test_format_yaml.py b/tests/test_format_yaml.py
new file mode 100644
index 0000000..f1c8e6c
--- /dev/null
+++ b/tests/test_format_yaml.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from jinja2cli import cli
+
+YamlParserFn, YamlExpectException, YamlRaiseException = cli.get_format("yaml")
+
+
+@pytest.mark.parametrize(
+ ("yaml", "yaml_data"),
+ [
+ ("", None),
+ ("foo", "foo"),
+ ("foo: \n", {"foo": None}),
+ ("foo: bar", {"foo": "bar"}),
+ ("foo: bar\nham: spam", {"foo": "bar", "ham": "spam"}),
+ ("foo: |\n bar\n ham\n spam", {"foo": "bar\nham\nspam"}),
+ ("foo: >\n bar\n ham\n spam", {"foo": "bar ham spam"}),
+ ("foo: \n- bar\n- ham\n- spam", {"foo": ["bar", "ham", "spam"]}),
+ ("- foo", ["foo"]),
+ ],
+)
+def test_yaml(yaml, yaml_data):
+ assert YamlParserFn(yaml) == yaml_data
+
+
+@pytest.mark.parametrize(
+ ("data"),
+ [
+ ("foo: \nbar"),
+ ("foo: |\nbar"),
+ ],
+)
+def test_yaml_errors(data):
+ with pytest.raises(YamlExpectException):
+ YamlParserFn(data)
diff --git a/tests/test_opt_d_variables.py b/tests/test_opt_d_variables.py
new file mode 100644
index 0000000..01219fa
--- /dev/null
+++ b/tests/test_opt_d_variables.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+
+import pytest
+
+from jinja2cli import cli
+
+from .common import run_jinja2
+
+TEMPLATE = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), "files/template.j2"
+)
+
+
+def test_d_variables():
+ out, err = run_jinja2(TEMPLATE, args=("-D", "title=foo"))
+ assert "" == err
+ assert "foo" == out
+
+
+def test_d_variables_overrides_other_data():
+ out, err = run_jinja2(
+ TEMPLATE, args=("-D", "title=foo"), input_data="title: bar"
+ )
+ assert "" == err
+ assert "foo" == out
diff --git a/tests/test_opt_extensions.py b/tests/test_opt_extensions.py
new file mode 100644
index 0000000..abd6c26
--- /dev/null
+++ b/tests/test_opt_extensions.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+import pytest
+
+from jinja2cli import cli
+
+from .common import is_python_version, run_jinja2, to_unicode
+
+TEMPLATE_CONTENT = "{% uppercase %}{{title}}{% enduppercase %}"
+
+
+def test_opt_extensions_short(tmp_path):
+ template_file = tmp_path / "template.j2"
+
+ if is_python_version(2):
+ template_file.write_text(unicode(TEMPLATE_CONTENT))
+ else:
+ template_file.write_text(TEMPLATE_CONTENT)
+
+ out, err = run_jinja2(
+ str(template_file),
+ args=("-e", "common.UpperCaseExtension"),
+ input_data="title: foo",
+ )
+
+ assert "" == err
+ assert "FOO" == out
+
+
+def test_opt_extensions_long(tmp_path):
+ template_file = tmp_path / "template.j2"
+
+ if is_python_version(2):
+ template_file.write_text(unicode(TEMPLATE_CONTENT))
+ else:
+ template_file.write_text(TEMPLATE_CONTENT)
+
+ out, err = run_jinja2(
+ str(template_file),
+ args=("--extension", "common.UpperCaseExtension"),
+ input_data="title: foo",
+ )
+
+ assert "" == err
+ assert "FOO" == out
diff --git a/tests/test_opt_format.py b/tests/test_opt_format.py
new file mode 100644
index 0000000..31c0923
--- /dev/null
+++ b/tests/test_opt_format.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+
+from .common import run_jinja2
+
+TEMPLATE = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), "files/template.j2"
+)
+
+
+def test_opt_format_short(tmp_path):
+ datafile = tmp_path / "datafile.env"
+ datafile.write_text("title=foo")
+
+ out, err = run_jinja2(TEMPLATE, str(datafile), ("-f", "env"))
+ assert "" == err
+ assert "foo" == out
+
+
+def test_opt_format_long(tmp_path):
+ datafile = tmp_path / "datafile.env"
+ datafile.write_text("title=foo")
+
+ out, err = run_jinja2(TEMPLATE, str(datafile), ("--format", "env"))
+ assert "" == err
+ assert "foo" == out
diff --git a/tests/test_opt_format_auto.py b/tests/test_opt_format_auto.py
new file mode 100644
index 0000000..7849b88
--- /dev/null
+++ b/tests/test_opt_format_auto.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+
+from .common import run_jinja2
+
+TEMPLATE = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), "files/template.j2"
+)
+
+
+def test_opt_format_auto_implicit(tmp_path):
+ datafile = tmp_path / "datafile.env"
+ datafile.write_text("title=foo")
+
+ out, err = run_jinja2(TEMPLATE, str(datafile))
+ assert "" == err
+ assert "foo" == out
+
+
+def test_opt_format_auto_explicit(tmp_path):
+ datafile = tmp_path / "datafile.env"
+ datafile.write_text("title=foo")
+
+ out, err = run_jinja2(TEMPLATE, str(datafile), ("-f", "auto"))
+ assert "" == err
+ assert "foo" == out
diff --git a/tests/test_opt_outfile.py b/tests/test_opt_outfile.py
new file mode 100644
index 0000000..78f40d1
--- /dev/null
+++ b/tests/test_opt_outfile.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+
+import pytest
+
+from jinja2cli import cli
+
+from .common import run_jinja2
+
+TEMPLATE = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), "files/template.j2"
+)
+
+
+def test_opt_outfile_short(tmp_path):
+ outfile = tmp_path / "outfile"
+ out, err = run_jinja2(
+ TEMPLATE, args=("-o", str(outfile)), input_data="title: foo"
+ )
+
+ assert "" == err
+ assert "" == out
+ assert "foo" == outfile.read_text()
+
+
+def test_opt_outfile_long(tmp_path):
+ outfile = tmp_path / "outfile"
+ out, err = run_jinja2(
+ TEMPLATE, args=("--outfile", str(outfile)), input_data="title: foo"
+ )
+
+ assert "" == err
+ assert "" == out
+ assert "foo" == outfile.read_text()
diff --git a/tests/test_opt_section.py b/tests/test_opt_section.py
new file mode 100644
index 0000000..cbad1d6
--- /dev/null
+++ b/tests/test_opt_section.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+
+import pytest
+
+from jinja2cli import cli
+
+from .common import run_jinja2
+
+TEMPLATE = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), "files/template.j2"
+)
+
+DATA_W_SECTIONS = (
+ """{"title_data": {"title": "foo"}, "other_data": {"title": "bar"}}"""
+)
+
+
+def test_section_short():
+ out, err = run_jinja2(
+ TEMPLATE, args=("-s", "title_data"), input_data=DATA_W_SECTIONS
+ )
+ assert "" == err
+ assert "foo" == out
+
+
+def test_section_long():
+ out, err = run_jinja2(
+ TEMPLATE, args=("--section", "title_data"), input_data=DATA_W_SECTIONS
+ )
+ assert "" == err
+ assert "foo" == out
diff --git a/tests/test_opt_strict.py b/tests/test_opt_strict.py
new file mode 100644
index 0000000..b9a478c
--- /dev/null
+++ b/tests/test_opt_strict.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+
+import pytest
+
+from jinja2cli import cli
+
+from .common import run_jinja2
+
+TEMPLATE = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), "files/template.j2"
+)
+
+UNDEFINED_ERROR = "jinja2.exceptions.UndefinedError: 'title' is undefined"
+
+
+def test_strict_enabled():
+ out, err = run_jinja2(TEMPLATE, args=("--strict",))
+ assert UNDEFINED_ERROR in err
+ assert "" == out
+
+
+def test_strict_disabled():
+ out, err = run_jinja2(TEMPLATE)
+ assert "" == err
+ assert "" == out
diff --git a/tests/test_parse_qs.py b/tests/test_parse_qs.py
deleted file mode 100644
index 0ebbb89..0000000
--- a/tests/test_parse_qs.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import pytest
-
-from jinja2cli import cli
-
-QS_PARSER_FN, QS_EXCEPT_EXC, QS_RAISE_EXC = cli.get_format("querystring")
-
-
-@pytest.mark.parametrize(
- ("qs", "qs_data"),
- [
- ("", dict()),
- ("foo=", dict()),
- ("foo=bar", dict(foo="bar")),
- ("foo=bar&ham=spam", dict(foo="bar", ham="spam")),
- ("foo.bar=ham&ham.spam=eggs", dict(foo=dict(bar="ham"), ham=dict(spam="eggs"))),
- ("foo=bar%20ham%20spam", dict(foo="bar ham spam")),
- ("foo=bar%2Eham%2Espam", dict(foo="bar.ham.spam")),
- ],
-)
-def test_parse_qs(qs, qs_data):
- assert QS_PARSER_FN(qs) == qs_data
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..4d9bf5e
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,45 @@
+[tox]
+minversion = 3.28
+requires =
+ virtualenv<20.22.0
+ tox-pyenv-redux==0.1.1
+
+env_list = py{27 ,35, 36, 37, 38, 39, 310, 311, 312}
+
+[testenv]
+description = run the tests with pytest
+pyenv_discovery = fallback
+deps =
+ -r {toxinidir}/requirements.txt
+ -r {toxinidir}/requirements_dev.txt
+
+commands =
+ pytest {tty:--color=yes} {posargs:-vv tests}
+ python --version
+
+[testenv:py27]
+basepython = python2.7
+
+[testenv:py35]
+basepython = python3.5
+
+[testenv:py36]
+basepython = python3.6
+
+[testenv:py37]
+basepython = python3.7
+
+[testenv:py38]
+basepython = python3.8
+
+[testenv:py39]
+basepython = python3.9
+
+[testenv:py310]
+basepython = python3.10
+
+[testenv:py311]
+basepython = python3.11
+
+[testenv:py312]
+basepython = python3.12