diff --git a/CHANGES.md b/CHANGES.md index 75de777..17caa15 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,27 @@ +Version 5.0 +----------- + +Major release, unreleased + +- Dropped support for Python 2.7. +- The option `--keep-trailing-newline` was removed in favor of making + it default. The old behaviour can be achieved with the new option + `--remove-trailing-newline`. +- Fixed Jinja 2.11.x compatability issue (gh-60). + +Version 4.4 +----------- + +Minor release (last with Python 2.7 support), unreleased + +- Fixed an exit code in case of undefined variable from 0 to 1. +- Fixed a bug that caused extension classes not to load. +- Quoted string variable with commas is not converted to list anymore (gh-57). +- Implemented workaround for Jinja 2.11 compatability issue (gh-60) +- Added support for INI and CSV file parsing. +- Fixed a bug that caused Yasha to crash when loading file extensions + (regression likely caused by Click). + Version 4.3 ----------- diff --git a/LICENSE.txt b/LICENSE.txt index f3b1140..f8e631f 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index c5a7612..b929271 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Usage: yasha [OPTIONS] [TEMPLATE_VARIABLES]... TEMPLATE Options: -o, --output FILENAME Place the rendered template into FILENAME. -v, --variables FILENAME Read template variables from FILENAME. Built- - in parsers are JSON, YAML, TOML and XML. + in parsers are JSON, YAML, TOML, INI, XML and CSV. -e, --extensions FILENAME Read template extensions from FILENAME. A Python file is expected. -c, --encoding TEXT Default is UTF-8. @@ -80,7 +80,7 @@ Options: ## Template variables -Template variables can be defined in a separate file. By default [JSON](http://www.json.org), [YAML](http://www.yaml.org/start.html), [TOML](https://github.com/toml-lang/toml) and [XML](https://github.com/martinblech/xmltodict) formats are supported. +Template variables can be defined in a separate file. By default [JSON](http://www.json.org), [YAML](http://www.yaml.org/start.html), [TOML](https://github.com/toml-lang/toml), [INI](https://docs.python.org/3/library/configparser.html#supported-ini-file-structure), [XML](https://github.com/martinblech/xmltodict) and [CSV](https://tools.ietf.org/html/rfc4180#section-2) formats are supported. ```bash yasha -v variables.yaml template.j2 @@ -98,6 +98,58 @@ Additionally you may define variables as part of the command-line call. A variab yasha --foo=bar -v variables.yaml template.j2 ``` +### CSV files + +to use the data stored in a csv variable file in templates, the name of the variable in the template has to match the name of the csv variable file. + +For example, consider the following template and variable files + +``` +template.j2 +mydata.csv +``` + +And the following contents in `mydata.csv` + +```csv +cell1,cell2,cell3 +cell4,cell5,cell6 +``` + +to access the rows of cells, you use the following syntax in your template (note that 'mydata' here matches the file name of the csv file) + +```jinja2 +{% for row in mydata %} +cell 1's value is {{row[0]}}, +cell 2's value is {{row[1]}} +{% endfor %} +``` + +By default, each row in the csv file is accessed in the template as a list of values (`row[0]`, `row[1]`, etc). +You can make each row accessible instead as a mapping by adding a header to the csv file. + +For example, consider the following contents of `mydata.csv` + +```csv +first_column,column2,third column +cell1,cell2,cell3 +cell4,cell5,cell6 +``` + +and the following Jinja template + +```jinja2 +{% for row in mydata %} +cell 1's value is {{row.first_column}}, +cell 2's value is {{row.column2}}, +cell 3's value is {{row['third column']}} +{% endfor %} +``` + +As you can see, cells can be accessed by column name instead of column index. +If the column name has no spaces in it, the cell can be accessed with 'dotted notation' (ie `row.first_column`) or 'square-bracket notation' (ie `row['third column']`. +If the column name has a space in it, the cell can only be accessed with 'square-bracket notation' + ### Automatic file variables look up If no variable file is explicitly given, Yasha will look for one by searching for a file named in the same way than the corresponding template but with the file extension either `.json`, `.yaml`, `.yml`, `.toml`, or `.xml`. @@ -354,13 +406,19 @@ cat template.j2 | yasha -v variables.yaml - Variables given as part of the command-line call can be Python literals, e.g. a list would be defined like this ```bash -yasha --foo="['foo', 'bar', 'baz']" template.j2 +yasha --lst="['foo', 'bar', 'baz']" template.j2 ``` The following is also interpreted as a list ```bash -yasha --foo=foo,bar,baz template.j2 +yasha --lst=foo,bar,baz template.j2 +``` + +Note that in case you like to pass a string with commas as a variable you have to quote it as + +```bash +yasha --str='"foo,bar,baz"' template.j2 ``` Other possible literals are: diff --git a/setup.py b/setup.py index b87c8c5..32f8794 100644 --- a/setup.py +++ b/setup.py @@ -20,10 +20,11 @@ include_package_data=True, install_requires=[ "Click", - "Jinja2", + "Jinja2<2.11", "pytoml", "pyyaml", "xmltodict", + 'configparser;python_version<"3.5"' ], entry_points=''' [console_scripts] diff --git a/tests/conftest.py b/tests/conftest.py index 9a38cca..23787a6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -24,10 +24,10 @@ import pytest import os.path +import sys SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__)) - @pytest.fixture def fixtures_dir(): return os.path.join(SCRIPT_PATH, "fixtures") diff --git a/tests/fixtures/nrf51.rs.jinja b/tests/fixtures/nrf51.rs.jinja index 76c3054..3904f3b 100644 --- a/tests/fixtures/nrf51.rs.jinja +++ b/tests/fixtures/nrf51.rs.jinja @@ -1,6 +1,6 @@ {#- The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/tests/test_build_automation_c.py b/tests/test_build_automation_c.py index a4af44d..6ac5317 100644 --- a/tests/test_build_automation_c.py +++ b/tests/test_build_automation_c.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -28,6 +28,9 @@ import pytest +if sys.version_info[0] == 2: + FileNotFoundError = IOError + SCRIPT_PATH = path.dirname(path.realpath(__file__)) requires_py27_or_py35_or_greater = pytest.mark.skipif( diff --git a/tests/test_cli.py b/tests/test_cli.py index 0ffd0ad..aba0a4c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,10 +22,23 @@ THE SOFTWARE. """ -import pytest from os import path, chdir import subprocess from subprocess import call, check_output +from textwrap import dedent +import sys + +import pytest +from yasha.cli import cli +from click.testing import CliRunner + + +def wrap(text): + return dedent(text).lstrip() + + +PY2 = True if sys.version_info[0] == 2 else False + @pytest.fixture(params=('json', 'yaml', 'yml', 'toml')) def vartpl(request): @@ -39,24 +52,162 @@ def vartpl(request): return (template[fmt], fmt) -@pytest.fixture -def varfile(vartpl, tmpdir): - content = {'int': 1} - template, filext = vartpl - file = tmpdir.join('variables.{}'.format(filext)) - file.write(template.format(**content)) - return file - - -def test_explicit_variable_file(tmpdir, varfile): +@pytest.fixture(params=('json', 'yaml', 'yml', 'toml', 'ini', 'csv', 'csv_with_header')) +def testdata(request): + templates = dict( + default=wrap(""" + {% for item in list_data %} + "{{ item.key1 }}"=>{{ item.key2 }} + {% endfor %} + {{ a_variable }} + {{ a.nested.variable }}"""), + # Ini files don't support the kinds of arbitrarily nested data structures found in the default template, + # so they can only be tested with a template which uses data structured in ini-format (ie a dict (the ini file) + # of dicts(the sections of the ini file) of keys (whose values can be None or strings) + ini=wrap(""" + Section One, variable one: {{ section_one.variable_one }} + {{ section_two.key }}"""), + # CSV files don't support the kinds of arbitrarily nested data structures found in the default template, + # so they can only be tested with a template which uses data structured in csv-format + # ie. a list of dicts if the csv file has a header row, a list of lists if it doesn't + csv=wrap(""" + {% for row in data %} + cell 1 is {{ row[0] }}, cell 2 is {{ row[1] }} + {% endfor %}"""), + csv_with_header=wrap(""" + {% for row in data %} + cell 1 is {{ row.first_column }}, cell 2 is {{ row['second column'] }} + {% endfor %}""") + ) + output = dict( + default=wrap(""" + "some value"=>key2 value + "another value"=>another key2 value + a variable value + a nested value"""), + ini=wrap(""" + Section One, variable one: S1 V1 value + S2 key value"""), + csv=wrap(""" + cell 1 is value1, cell 2 is 2 + cell 1 is value3, cell 2 is 4 + cell 1 is value5, cell 2 is 6 + cell 1 is value7, cell 2 is 8 + cell 1 is value9, cell 2 is 10 + """) + ) + data = dict( + # Each entry is a list of strings [template, expected_output, data, extension] + json=[ + templates['default'], + output['default'], + wrap(""" + { + "list_data": [ + { + "key1": "some value", + "key2": "key2 value" + }, + { + "key1": "another value", + "key2": "another key2 value" + } + ], + "a_variable": "a variable value", + "a": { + "nested": { + "variable": "a nested value" + } + } + }"""), + 'json' + ], + yaml=[ + templates['default'], + output['default'], + wrap(""" + list_data: + - key1: some value + key2: key2 value + - key1: another value + key2: another key2 value + a_variable: a variable value + a: + nested: + variable: a nested value + """), + 'yaml' + ], + toml=[ + templates['default'], + output['default'], + wrap(""" + a_variable = "a variable value" + [[list_data]] + key1 = "some value" + key2 = "key2 value" + [[list_data]] + key1 = "another value" + key2 = "another key2 value" + [a.nested] + variable = "a nested value" + """), + 'toml' + ], + ini=[ + templates['ini'], + output['ini'], + wrap(""" + [section_one] + variable_one = S1 V1 value + [section_two] + key = S2 key value + """), + 'ini' + ], + csv=[ + templates['csv'], + output['csv'], + wrap(""" + value1,2 + value3,4 + value5,6 + value7,8 + value9,10"""), + 'csv' + ], + csv_with_header=[ + templates['csv_with_header'], + output['csv'], + wrap(""" + first_column,second column + value1,2 + value3,4 + value5,6 + value7,8 + value9,10"""), + 'csv' + ] + ) + data['yml'] = data['yaml'] + data['yml'][3] = 'yml' + fmt = request.param + return data[fmt] + + +def test_explicit_variable_file(tmpdir, testdata): + template, expected_output, data, extension = testdata tpl = tmpdir.join('template.j2') - tpl.write('{{ int }}') + tpl.write(template) + datafile = tmpdir.join('data.{}'.format(extension)) + datafile.write(data) - errno = call(('yasha', '-v', str(varfile), str(tpl))) - assert errno == 0 + runner = CliRunner() + result = runner.invoke(cli, ['-v', str(datafile), str(tpl)]) + assert result.exit_code == 0 output = tmpdir.join('template') - assert output.read() == '1' + assert output.read() == expected_output def test_two_explicitly_given_variables_files(tmpdir): @@ -72,8 +223,9 @@ def test_two_explicitly_given_variables_files(tmpdir): b = tmpdir.join('b.toml') b.write('b = 2\nc = 3') - errno = call(('yasha', '-v', str(a), '-v', str(b), str(tpl))) - assert errno == 0 + runner = CliRunner() + result = runner.invoke(cli, ['-v', str(a), '-v', str(b), str(tpl)]) + assert result.exit_code == 0 output = tmpdir.join('template') assert output.read() == '6' # a + b + c = 1 + 2 + 3 = 6 @@ -98,8 +250,9 @@ def test_variable_file_lookup(tmpdir, vartpl): varfile = tmpdir.join(varfile) varfile.write(vartpl[0].format(int=i)) - errno = call(('yasha', 'sub/foo.c.j2')) - assert errno == 0 + runner = CliRunner() + result = runner.invoke(cli, ['sub/foo.c.j2']) + assert result.exit_code == 0 assert path.isfile('sub/foo.c') output = tmpdir.join('sub/foo.c') @@ -152,8 +305,9 @@ def parse_xml(file): file = tmpdir.join("foo.j2ext") file.write(extensions) - errno = call(["yasha", "foo.toml.jinja"]) - assert errno == 0 + runner = CliRunner() + result = runner.invoke(cli, ['foo.toml.jinja']) + assert result.exit_code == 0 assert path.isfile("foo.toml") o = tmpdir.join("foo.toml") @@ -180,11 +334,11 @@ def test_broken_extensions(tmpdir): ext = tmpdir.join("foo.j2ext") ext.write(extensions) - with pytest.raises(CalledProcessError) as e: - cmd = ["yasha", "foo.jinja"] - check_output(cmd, stderr=STDOUT) - assert e.value.returncode == 1 - assert b"Invalid syntax (foo.j2ext, line 1)" in e.value.output + runner = CliRunner() + result = runner.invoke(cli, ['foo.jinja']) + assert result.exit_code == 1 + assert result.exception + assert "Invalid syntax (foo.j2ext, line 1)" in result.stdout def test_broken_extensions_name_error(tmpdir): @@ -199,15 +353,15 @@ def test_broken_extensions_name_error(tmpdir): ext = tmpdir.join("foo.j2ext") ext.write(extensions) - with pytest.raises(CalledProcessError) as e: - cmd = ["yasha", "foo.jinja"] - check_output(cmd, stderr=STDOUT) - assert e.value.returncode == 1 - assert b"name 'asd' is not defined" in e.value.output + runner = CliRunner() + result = runner.invoke(cli, ['foo.jinja']) + assert result.exit_code == 1 + assert result.exception + assert "name 'asd' is not defined" in result.stdout def test_render_template_from_stdin_to_stdout(): - cmd = r'echo -n "{{ foo }}" | yasha --foo=bar -' + cmd = r'echo {{ foo }} | yasha --foo=bar -' out = check_output(cmd, shell=True) assert out == b'bar' @@ -225,21 +379,23 @@ def test_json_template(tmpdir): def test_mode_is_none(): """gh-42, and gh-44""" - cmd = r'echo -n "{{ foo }}" | yasha -' + cmd = r'echo {{ foo }} | yasha -' out = check_output(cmd, shell=True) assert out == b'' def test_mode_is_pedantic(): - """gh-42""" - cmd = r'echo -n "{{ foo }}" | yasha --mode=pedantic -' - out = check_output(cmd, shell=True, stderr=subprocess.STDOUT) - assert out == b"UndefinedError: 'foo' is undefined\n" + """gh-42, and gh-48""" + with pytest.raises(subprocess.CalledProcessError) as err: + cmd = r'echo {{ foo }} | yasha --mode=pedantic -' + out = check_output(cmd, shell=True, stderr=subprocess.STDOUT) + out = err.value.output + assert out == b"Error: Variable 'foo' is undefined\n" def test_mode_is_debug(): """gh-44""" - cmd = r'echo -n "{{ foo }}" | yasha --mode=debug -' + cmd = r'echo {{ foo }} | yasha --mode=debug -' out = check_output(cmd, shell=True) assert out == b'{{ foo }}' @@ -279,3 +435,17 @@ def test_template_syntax_for_latex(tmpdir): out = check_output(('yasha', '--keep-trailing-newline', '-e', str(ext), '-o-', str(tpl))) assert out.decode() == expected_output + + +def test_extensions_file_with_do(tmpdir): + """gh-52""" + tmpdir.chdir() + + extensions = tmpdir.join('extensions.py') + extensions.write('from jinja2.ext import do') + + tmpl = tmpdir.join('template.j2') + tmpl.write(r'{% set list = [1, 2, 3] %}{% do list.append(4) %}{{ list }}') + + out = check_output(('yasha', '-e', str(extensions), '-o-', str(tmpl))) + assert out == b'[1, 2, 3, 4]' diff --git a/tests/test_filters.py b/tests/test_filters.py index fd3c874..bd8450e 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -83,7 +83,7 @@ def test_subprocess_with_unknown_cmd(): template = '{{ "unknown_cmd" | subprocess }}' out, retcode = check_output('yasha', '-', stdin=template) assert retcode != 0 - assert b'unknown_cmd: not found' in out + assert b'not found' in out @requires_py3 diff --git a/tests/test_parsers_svd.py b/tests/test_parsers_svd.py index b95d3b5..6988e73 100644 --- a/tests/test_parsers_svd.py +++ b/tests/test_parsers_svd.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/tests/test_template_variables.py b/tests/test_template_variables.py index b249746..f7c88c1 100644 --- a/tests/test_template_variables.py +++ b/tests/test_template_variables.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -76,3 +76,15 @@ def test_dictionary(t): t.write("{{ var is mapping }}, {% for k in 'abc' %}{{ var[k] }}{% endfor %}") out = check_output(['yasha', '--var', "{'a': 1, 'b': 2, 'c': 3}", '-o-', str(t)]) assert out == b'True, 123' + +def test_commas_in_quoted_string(t): + """ gh-57 """ + t.write('{{ str is string }}, {{ str }}') + out = check_output(['yasha', '--str', '\'"foo,bar,baz"\'', '-o-', str(t)]) + assert out == b'True, foo,bar,baz' + +def test_quoted_comma_in_comma_separated_list(t): + """ gh-57 """ + t.write('{{ lst is sequence }}, {{ lst | join(".") }}') + out = check_output(['yasha', '--lst', '\'"foo,bar",baz\'', '-o-', str(t)]) + assert out == b'True, foo,bar.baz' diff --git a/yasha/__init__.py b/yasha/__init__.py index 45f4eb0..175e371 100644 --- a/yasha/__init__.py +++ b/yasha/__init__.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/yasha/classes.py b/yasha/classes.py index 046c4af..f6dbe7f 100644 --- a/yasha/classes.py +++ b/yasha/classes.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/yasha/cli.py b/yasha/cli.py index 884dad3..0af98bc 100644 --- a/yasha/cli.py +++ b/yasha/cli.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -59,8 +59,10 @@ def load_python_module(file): module = loader.load_module() except ImportError: # Fallback to Python2 import imp - desc = (".py", "rb", imp.PY_SOURCE) - module = imp.load_module('yasha_extensions', file, file.name, desc) + with open(file.name) as f: + desc = (".py", "rb", imp.PY_SOURCE) + module = imp.load_module('yasha_extensions', f, file.name, desc) + pass return module def load_extensions(file): @@ -118,6 +120,11 @@ def load_extensions(file): except AttributeError: PARSERS.update(parsers) + try: + CLASSES.extend(module.CLASSES) + except AttributeError: + CLASSES.extend(classes) + @click.command(context_settings=dict( help_option_names=["-h", "--help"], @@ -243,4 +250,4 @@ def cli( t_stream.enable_buffering(size=5) t_stream.dump(output, encoding=yasha.ENCODING) except JinjaUndefinedError as e: - click.echo("UndefinedError: {}".format(e), err=True) + raise ClickException("Variable {}".format(e)) diff --git a/yasha/cmsis.py b/yasha/cmsis.py index bbef018..b743be8 100644 --- a/yasha/cmsis.py +++ b/yasha/cmsis.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/yasha/filters.py b/yasha/filters.py index 7fdeed7..9e95351 100644 --- a/yasha/filters.py +++ b/yasha/filters.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/yasha/parsers.py b/yasha/parsers.py index 6209fd2..ecf718d 100644 --- a/yasha/parsers.py +++ b/yasha/parsers.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,6 +22,7 @@ THE SOFTWARE. """ +import sys from .yasha import ENCODING @@ -60,6 +61,38 @@ def parse_svd(file): "peripherals": svd.peripherals, } + +def parse_ini(file): + from configparser import ConfigParser + cfg = ConfigParser() + # yasha opens files in binary mode, configparser expects files in text mode + content = file.read().decode(ENCODING) + cfg.read_string(content) + result = dict(cfg) + for section, data in result.items(): + result[section] = dict(data) + return result + + +def parse_csv(file): + from csv import reader, DictReader, Sniffer + from io import TextIOWrapper + from os.path import basename, splitext + assert file.name.endswith('.csv') + name = splitext(basename(file.name))[0] # get the filename without the extension + content = TextIOWrapper(file, encoding='utf-8', errors='replace') + sample = content.read(1024) + content.seek(0) + csv = list() + if Sniffer().has_header(sample): + for row in DictReader(content): + csv.append(dict(row)) + else: + for row in reader(content): + csv.append(row) + return {name: csv} + + PARSERS = { '.json': parse_json, '.yaml': parse_yaml, @@ -67,4 +100,6 @@ def parse_svd(file): '.toml': parse_toml, '.xml': parse_xml, '.svd': parse_svd, + '.ini': parse_ini, + '.csv': parse_csv } diff --git a/yasha/scons.py b/yasha/scons.py index 5503d22..b2406d9 100644 --- a/yasha/scons.py +++ b/yasha/scons.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/yasha/tests.py b/yasha/tests.py index 9598888..9a32cc0 100644 --- a/yasha/tests.py +++ b/yasha/tests.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/yasha/yasha.py b/yasha/yasha.py index f563668..6010620 100644 --- a/yasha/yasha.py +++ b/yasha/yasha.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) -Copyright (c) 2015-2017 Kim Blomqvist +Copyright (c) 2015-2020 Kim Blomqvist Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -24,6 +24,8 @@ import os import ast +import csv +import jinja2 as jinja __version__ = "4.3" @@ -114,9 +116,13 @@ def parse_cli_variables(args): pass except SyntaxError: pass - if isinstance(val, str) and ',' in val: - # Convert foo,bar,baz to list ['foo', 'bar', 'baz'] - val = val.split(',') + if isinstance(val, str): + # Convert foo,bar,baz to list ['foo', 'bar', 'baz'] and + # '"foo,bar,baz"' to string 'foo,bar,baz' + reader = csv.reader([val], delimiter=',', quotechar='"') + val = list(reader)[0] + if len(val) == 1: + val = val[0] variables[opt] = val return variables @@ -124,34 +130,35 @@ def parse_cli_variables(args): def load_jinja( path, tests, filters, classes, mode, trim_blocks, lstrip_blocks, keep_trailing_newline): - from jinja2 import Environment, FileSystemLoader - from jinja2 import Undefined, StrictUndefined, DebugUndefined - - if mode == 'pedantic': - undefined = StrictUndefined - elif mode == 'debug': - undefined = DebugUndefined - else: - undefined = Undefined - from jinja2.defaults import BLOCK_START_STRING, BLOCK_END_STRING, \ VARIABLE_START_STRING, VARIABLE_END_STRING, \ - COMMENT_START_STRING, COMMENT_END_STRING + COMMENT_START_STRING, COMMENT_END_STRING, \ + LINE_STATEMENT_PREFIX, LINE_COMMENT_PREFIX, \ + NEWLINE_SEQUENCE - jinja = Environment( - loader=FileSystemLoader(path), - extensions=classes, - trim_blocks=trim_blocks, - lstrip_blocks=lstrip_blocks, - keep_trailing_newline=keep_trailing_newline, - undefined=undefined, + undefined = { + 'pedantic': jinja.StrictUndefined, + 'debug': jinja.DebugUndefined, + None: jinja.Undefined, + } + + env = jinja.Environment( block_start_string=BLOCK_START_STRING, block_end_string=BLOCK_END_STRING, variable_start_string=VARIABLE_START_STRING, variable_end_string=VARIABLE_END_STRING, comment_start_string=COMMENT_START_STRING, - comment_end_string=COMMENT_END_STRING + comment_end_string=COMMENT_END_STRING, + line_statement_prefix=LINE_STATEMENT_PREFIX, + line_comment_prefix=LINE_COMMENT_PREFIX, + trim_blocks=trim_blocks, + lstrip_blocks=lstrip_blocks, + newline_sequence=NEWLINE_SEQUENCE, + keep_trailing_newline=keep_trailing_newline, + extensions=classes, + undefined=undefined[mode], + loader=jinja.FileSystemLoader(path) ) - jinja.tests.update(tests) - jinja.filters.update(filters) - return jinja + env.tests.update(tests) + env.filters.update(filters) + return env