From 07e218cc013ff548398c2ec48979545dbc5cbd23 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Mon, 6 Jul 2026 19:29:28 +0200 Subject: [PATCH] feat: first version --- README.md | 503 +++++++++++++++++- locale/fr/LC_MESSAGES/rougail_output_hcl.po | 31 ++ locale/rougail_output_hcl.pot | 25 + pyproject.toml | 43 ++ src/rougail/output_hcl/__init__.py | 26 + src/rougail/output_hcl/__version__.py | 1 + src/rougail/output_hcl/config.py | 31 ++ src/rougail/output_hcl/export.py | 163 ++++++ src/rougail/output_hcl/i18n.py | 26 + .../fr/LC_MESSAGES/rougail_output_hcl.mo | Bin 0 -> 681 bytes src/rougail/structural_hcl/__init__.py | 22 + src/rougail/structural_hcl/annotator.py | 82 +++ src/rougail/structural_hcl/config.py | 44 ++ src/rougail/structural_hcl/object_model.py | 39 ++ tests/errors/00-label_outside_block.yaml | 9 + .../errors/00-label_outside_block.yaml.errno | 1 + tests/results/00-doc_primitive.yaml.hcl | 4 + tests/results/01-doc_complex_types.yaml.hcl | 8 + tests/results/01-doc_complex_types2.yaml.hcl | 10 + .../results/02-doc_expressions_basic.yaml.hcl | 3 + .../03-doc_conditional_expressions.yaml.hcl | 2 + tests/results/04-doc_basic_block.yaml.hcl | 3 + tests/results/05-doc_block_in_block.yaml.hcl | 5 + .../06-doc_block_with_a_label.yaml.hcl | 3 + .../07-doc_block_with_some_labels.yaml.hcl | 3 + ...08-doc_block_inside_a_label_block.yaml.hcl | 5 + ...block_with_a_label_inside_a_block.yaml.hcl | 5 + ...with_a_label_inside_a_block_multi.yaml.hcl | 15 + tests/results/09-doc_variable_block.yaml.hcl | 4 + ...with_a_label_inside_a_block_multi.yaml.hcl | 10 + tests/results/92-examples.yaml.hcl | 24 + tests/results/93-examples.yaml.hcl | 16 + tests/results/94-examples.yaml.hcl | 24 + tests/results_subconfig/rougail.hcl | 3 + tests/structures/00-doc_primitive.yaml | 14 + tests/structures/01-doc_complex_types.yaml | 8 + tests/structures/01-doc_complex_types2.yaml | 20 + .../structures/02-doc_expressions_basic.yaml | 12 + .../03-doc_conditional_expressions.yaml | 11 + tests/structures/04-doc_basic_block.yaml | 10 + tests/structures/05-doc_block_in_block.yaml | 14 + .../structures/06-doc_block_with_a_label.yaml | 14 + .../07-doc_block_with_some_labels.yaml | 18 + .../08-doc_block_inside_a_label_block.yaml | 22 + ...doc_block_with_a_label_inside_a_block.yaml | 22 + ...ock_with_a_label_inside_a_block_multi.yaml | 32 ++ tests/structures/09-doc_variable_block.yaml | 19 + tests/structures/92-examples.yaml | 37 ++ tests/structures/93-examples.yaml | 28 + tests/structures/94-examples.yaml | 34 ++ tests/structures_subconfig/00-rougail.yaml | 21 + tests/test_error.py | 43 ++ tests/test_load.py | 48 ++ tests/test_subconfig.py | 51 ++ 54 files changed, 1669 insertions(+), 2 deletions(-) create mode 100644 locale/fr/LC_MESSAGES/rougail_output_hcl.po create mode 100644 locale/rougail_output_hcl.pot create mode 100644 pyproject.toml create mode 100644 src/rougail/output_hcl/__init__.py create mode 100644 src/rougail/output_hcl/__version__.py create mode 100644 src/rougail/output_hcl/config.py create mode 100644 src/rougail/output_hcl/export.py create mode 100644 src/rougail/output_hcl/i18n.py create mode 100644 src/rougail/output_hcl/locale/fr/LC_MESSAGES/rougail_output_hcl.mo create mode 100644 src/rougail/structural_hcl/__init__.py create mode 100644 src/rougail/structural_hcl/annotator.py create mode 100644 src/rougail/structural_hcl/config.py create mode 100644 src/rougail/structural_hcl/object_model.py create mode 100644 tests/errors/00-label_outside_block.yaml create mode 100644 tests/errors/00-label_outside_block.yaml.errno create mode 100644 tests/results/00-doc_primitive.yaml.hcl create mode 100644 tests/results/01-doc_complex_types.yaml.hcl create mode 100644 tests/results/01-doc_complex_types2.yaml.hcl create mode 100644 tests/results/02-doc_expressions_basic.yaml.hcl create mode 100644 tests/results/03-doc_conditional_expressions.yaml.hcl create mode 100644 tests/results/04-doc_basic_block.yaml.hcl create mode 100644 tests/results/05-doc_block_in_block.yaml.hcl create mode 100644 tests/results/06-doc_block_with_a_label.yaml.hcl create mode 100644 tests/results/07-doc_block_with_some_labels.yaml.hcl create mode 100644 tests/results/08-doc_block_inside_a_label_block.yaml.hcl create mode 100644 tests/results/09-doc_block_with_a_label_inside_a_block.yaml.hcl create mode 100644 tests/results/09-doc_block_with_a_label_inside_a_block_multi.yaml.hcl create mode 100644 tests/results/09-doc_variable_block.yaml.hcl create mode 100644 tests/results/91-block_with_a_label_inside_a_block_multi.yaml.hcl create mode 100644 tests/results/92-examples.yaml.hcl create mode 100644 tests/results/93-examples.yaml.hcl create mode 100644 tests/results/94-examples.yaml.hcl create mode 100644 tests/results_subconfig/rougail.hcl create mode 100644 tests/structures/00-doc_primitive.yaml create mode 100644 tests/structures/01-doc_complex_types.yaml create mode 100644 tests/structures/01-doc_complex_types2.yaml create mode 100644 tests/structures/02-doc_expressions_basic.yaml create mode 100644 tests/structures/03-doc_conditional_expressions.yaml create mode 100644 tests/structures/04-doc_basic_block.yaml create mode 100644 tests/structures/05-doc_block_in_block.yaml create mode 100644 tests/structures/06-doc_block_with_a_label.yaml create mode 100644 tests/structures/07-doc_block_with_some_labels.yaml create mode 100644 tests/structures/08-doc_block_inside_a_label_block.yaml create mode 100644 tests/structures/09-doc_block_with_a_label_inside_a_block.yaml create mode 100644 tests/structures/09-doc_block_with_a_label_inside_a_block_multi.yaml create mode 100644 tests/structures/09-doc_variable_block.yaml create mode 100644 tests/structures/92-examples.yaml create mode 100644 tests/structures/93-examples.yaml create mode 100644 tests/structures/94-examples.yaml create mode 100644 tests/structures_subconfig/00-rougail.yaml create mode 100644 tests/test_error.py create mode 100644 tests/test_load.py create mode 100644 tests/test_subconfig.py diff --git a/README.md b/README.md index 634bbbf..f11bd6b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,502 @@ -# rougail-output-hcl +--- +gitea: none +include_toc: true +--- -Rougail output to HCL2 format \ No newline at end of file +# HCL + +The HashiCorp Configuration Language (HCL) is a specialized configuration language developed by HashiCorp, primarily designed for use with its suite of tools, including Terraform, Vault, and Nomad. + +Since Rougail is designed to centralize the variables from your various projects, it may be useful to export the variables and values defined in Rougail to an HCL file. + +Please note that the goal here is not to manage all your HCL files, but to export the variables and values to them. + +## Attributes + +Attributes assigns a value to a particular name. + +An attribute is therefore the name of a variable or a family to which a value is assigned. + +### Primitive Types + +Value has those primitive types: `string`, `integer`, `bool` or `null` + +```hcl +a_string = "a value" +an_integer = 1 +a_boolean = true +null_value = null +``` + +Nothing particular in Rougail: + +```yaml +%YAML 1.2 +--- +version: 1.1 + +a_string: a value # An attribute with a string + +an_integer: 1 # An attribute with a integer + +a_boolean: true # A boolean attribute + +null_value: + description: A nullable value + mandatory: false +... +``` + +### Complex Types + +Value could be `list`, `set`, `tuple`, `map` or `object`. + +#### `list`, `set` or `tuple` + +The `set` type is an unordered `list` (the elements are all of the same type) with no duplicates. Rougail lists also do not allow duplicates by default (though this can be enabled), but their values are ordered. + +The type `tuple` (where each element has its own type) is not allowed in Rougail, so hasn't equivalent for now. + +Syntactically, there is no difference between a list, a set, and a tuple. + +In Rougail, you must therefore declare a variable of a specific type and set the “multi” parameter to True. + +```hcl +a_list = [ + "value 1", + "value 2", +] +an_integer_list = [ + 1, + 2, +] +``` + +```yaml +%YAML 1.2 +--- +version: 1.1 + +a_list: ["value 1", "value 2"] # An attribute with a list of strings + +an_integer_list: [1, 2] # An attribute with a list of integer +... +``` + +#### `map` or `object` + +The difference between `map` and `object` in HCL is analogous to that between `list` and `tuple`: the values in a `map` are all of the same type, whereas the values in an `object` can be of different. + +In Rougail, this corresponds to a family. + +```hcl +a_map = { + key_1 = "value 1" + key_2 = "value 2" + key_3 = "value 3" +} +an_object = { + a_string = "a value" + an_integer = 1 + a_boolean = true +} +``` + +```yaml +%YAML 1.2 +--- +version: 1.1 + +a_map: # A map is simply a collection of variables of the same type + + key_1: value 1 # A first attribute + + key_2: value 2 # A second attribute + + key_3: value 3 # A third attribute + +an_object: # A object is just a family + + a_string: a value # An attribute with a string + + an_integer: 1 # An attribute with a integer + + a_boolean: true # A boolean attribute +... +``` + +### Variable + +```hcl +a_first_string: "a value" +a_second_string: a_first_string +``` + +A string, for now, must be enclosed in quotes. But a variable must not be enclosed in quotes. + +You need to calculate the value directly in Rougail. + +```yaml +%YAML 1.2 +--- +version: 1.1 + +a_first_string: "a value" # Take a first value + +a_second_string: + description: An expressions is just a string + default: + variable: _.a_first_string +... +``` + +Or use basic expression (see after). + +### Expressions + +Expressions allow for dynamic configuration. + +#### Basic + +```hcl +a_first_string = "a value" +a_second_string = a_first_string +a_third_string = "combine ${a_first_string}" +``` + +A string, for now, must be enclosed in quotes. But a variable must not be enclosed in quotes (see below). + +You have tu use basic expression in this case: + +```yaml +%YAML 1.2 +--- +version: 1.1 + +a_first_string: "a value" # Take a first value + +a_second_string: + description: A variable could also use expression to get value + hcl_quoted_string: false + default: a_first_string + +a_third_string: combine ${a_first_string} # An expressions is just a string +... +``` + +#### Conditional Expressions + +```hcl +a_first_string = "a value" +a_second_string = a_first_string == "a value" ? 2 : 1 +``` + +A string, for now, must be enclosed in quotes. But a conditional expression must not be enclosed in quotes. + +You need to calculate the value directly in Rougail. + +```yaml +%YAML 1.2 +--- +version: 1.1 + +a_first_string: "a value" # Take a first value + +a_second_string: + description: An expressions is just a string + hcl_quoted_string: false + default: "a_first_string == \"a value\" ? 2 : 1" +... +``` + +#### Function + +HCL includes a variety of built-in functions that you can use to manipulate data and perform calculations. + +```hcl +content = file("config.json") +``` + +A string, for now, must be enclosed in quotes. But a conditional expression must not be enclosed in quotes. + +## Block + +A `block` is a container for configuration. + +### A basic block + +There is no equivalent to a `block` in Rougail. To define a `block`, you must define a family and add an `hcl_type` parameter set to `block`. + +[!NOTE] The `hcl` structural module must be loaded for this type to be recognized. + +```hcl +a_block { + an_attribute = "a value" +} +``` + +```yaml +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + an_attribute: a value # An attribute in a block +... +``` + +### A block in a block + +Blocks can have a tree-like structure, like family in Rougail. + +```hcl +a_block { + a_second_block { + an_attribute = "a value" + } +} +``` + +```yaml +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + a_second_block: + description: We can add a block inside a block + hcl_type: block + + an_attribute: a value # An attribute in a block +... +``` + +### A block with a label + +Blocks have a block type and can have zero or more labels. + +There is no equivalent to a `label` in Rougail. To define a `label`, you must define a family and add an `hcl_type` parameter set to `label`. A `label` must be defined within a `block`. + +[!NOTE] The `hcl` structural module must be loaded for this type to be recognized. + +```hcl +a_block "a_label" { + an_attribute = "a value" +} +``` + +```yaml +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + a_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + an_attribute: a value # An attribute in a block +... +``` + +### A block with some labels + +```hcl +a_block "a_first_label" "a_second_label" { + an_attribute = "a value" +} +``` + +```yaml +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + a_first_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + a_second_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + an_attribute: a value # An attribute in a block +... +``` + +### Block inside a label block + +A `block` could be in a `label` block. + +```hcl +a_block "a_first_label" "a_second_label" { + a_sub_block { + a_string = "a value" + } +} +``` + +```yaml +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + a_first_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + a_second_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + a_sub_block: + description: A block insible label block + hcl_type: block + + a_string: a value # An attribute in a block +... +``` + +### Block with a label inside a block + +And a `label` block could be in a `block`. + +```hcl +a_block { + a_sub_block "a_first_label" "a_second_label" { + a_string = "a value" + } +} +``` + +```yaml +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + a_sub_block: + description: A label block insible block + hcl_type: block + + a_first_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + a_second_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + a_string: a value # An attribute in a block +... +``` + +### Duplicate block name + +In Rougail, it's not possible to have two families with the same name. + +```hcl +a_block { + a_sub_block "a_label" { + an_attribut = [ + "value 1", + "value2", + ] + } + + + a_sub_block "a_label" { + an_attribut = [ + "value 3", + ] + } +} +``` + +```yaml +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + a_sub_block_1: + description: A label block insible block + hcl_type: block + hcl_name: a_sub_block + + a_label: + description: Add a label + hcl_type: label + + an_attribut: # An attribute + - value 1 + - value2 + + a_sub_block_2: + description: Add a label + hcl_type: block + hcl_name: a_sub_block + + a_label: + hcl_type: label + + an_attribut: # An attribute + - value 3 +... +``` + +### Variable block + +The variable block define variables within your HCL configuration. + +```hcl +variable "environment" { + description = "The deployment environment" + type = string +} +``` + +```yaml +%YAML 1.2 +--- +version: 1.1 + +variable: + description: Variable block + hcl_type: block + + environment: + description: The deployment environment + hcl_type: label + + description: The deployment environment # The environment description + + type: + description: The type description + hcl_quoted_string: false + default: string +... +``` diff --git a/locale/fr/LC_MESSAGES/rougail_output_hcl.po b/locale/fr/LC_MESSAGES/rougail_output_hcl.po new file mode 100644 index 0000000..938c1d1 --- /dev/null +++ b/locale/fr/LC_MESSAGES/rougail_output_hcl.po @@ -0,0 +1,31 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2026-07-06 16:47+0200\n" +"PO-Revision-Date: 2026-07-06 16:47+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" +"X-Generator: Poedit 3.9\n" + +#: src/rougail/output_hcl/export.py:130 +msgid "The family \"{0}\" has a hcl_type parameter but is not in a root config" +msgstr "" +"La famille \"{0}\" has le paramètre hcl_type mais n'est pas dans la config " +"racine" + +#: src/rougail/output_hcl/export.py:142 +msgid "" +"The family \"{0}\" has a hcl_type parameter to label but is not in a block" +msgstr "" +"La famille \"{0}\" has le paramètre hcl_type à label mais n'est pas dans un " +"block" diff --git a/locale/rougail_output_hcl.pot b/locale/rougail_output_hcl.pot new file mode 100644 index 0000000..b2728dd --- /dev/null +++ b/locale/rougail_output_hcl.pot @@ -0,0 +1,25 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2026-07-06 16:48+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" + + +#: src/rougail/output_hcl/export.py:130 +msgid "The family \"{0}\" has a hcl_type parameter but is not in a root config" +msgstr "" + +#: src/rougail/output_hcl/export.py:142 +msgid "The family \"{0}\" has a hcl_type parameter to label but is not in a block" +msgstr "" + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d2d8667 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,43 @@ +[build-system] +build-backend = "flit_core.buildapi" +requires = ["flit_core >=3.8.0,<4"] + +[project] +name = "rougail.output_hcl" +version = "0.0.0" +authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}] +readme = "README.md" +description = "Rougail output hcl" +requires-python = ">=3.11" +license = {file = "LICENSE"} +classifiers = [ + "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + "Natural Language :: English", + "Natural Language :: French", +] + +dependencies = [ + "rougail-base >= 1.2.0,<2", + "python-hcl2 >= 8.1.2,<9", +] + +[project.urls] +Home = "https://forge.cloud.silique.fr/stove/rougail-output-hcl" + +[tool.commitizen] +name = "cz_conventional_commits" +tag_format = "$version" +version_scheme = "pep440" +version_provider = "pep621" +version_files = [ + "src/rougail/output_hcl/__version__.py", + "pyproject.toml:version" +] +update_changelog_on_bump = true +changelog_merge_prerelease = true diff --git a/src/rougail/output_hcl/__init__.py b/src/rougail/output_hcl/__init__.py new file mode 100644 index 0000000..d8f9bb6 --- /dev/null +++ b/src/rougail/output_hcl/__init__.py @@ -0,0 +1,26 @@ +""" +Silique (https://www.silique.fr) +Copyright (C) 2026 + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +""" + +from .export import RougailOutputHcl +from .__version__ import __version__ + + +RougailOutput = RougailOutputHcl + + +__all__ = ("RougailOutputHcl", "__version__") diff --git a/src/rougail/output_hcl/__version__.py b/src/rougail/output_hcl/__version__.py new file mode 100644 index 0000000..6c8e6b9 --- /dev/null +++ b/src/rougail/output_hcl/__version__.py @@ -0,0 +1 @@ +__version__ = "0.0.0" diff --git a/src/rougail/output_hcl/config.py b/src/rougail/output_hcl/config.py new file mode 100644 index 0000000..ef2491c --- /dev/null +++ b/src/rougail/output_hcl/config.py @@ -0,0 +1,31 @@ +""" +Silique (https://www.silique.fr) +Copyright (C) 2026 + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +""" + + +def get_rougail_config( + *, + backward_compatibility=True, +) -> dict: + return { + "name": "hcl", + "process": "output", + "level": 70, + } + + +__all__ = ("get_rougail_config",) diff --git a/src/rougail/output_hcl/export.py b/src/rougail/output_hcl/export.py new file mode 100644 index 0000000..f52498d --- /dev/null +++ b/src/rougail/output_hcl/export.py @@ -0,0 +1,163 @@ +""" +Silique (https://www.silique.fr) +Copyright (C) 2026 + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +""" + +from typing import Any, Union +from hcl2 import dumps +from hcl2.deserializer import DeserializerOptions +from hcl2.formatter import FormatterOptions +from json import dumps as add_quote +from rougail.error import DictConsistencyError + + +from .i18n import _ +from ..output_json import RougailOutputJson + + +DEBUG = False +# DEBUG = True + + +class RougailOutputHcl(RougailOutputJson): + output_name = "hcl" + + def __init__( + self, + config: "Config", + *, + rougailconfig: "RougailConfig" = None, + **kwargs, + ) -> None: + super().__init__(config, rougailconfig=rougailconfig, **kwargs) + self.d_opts = DeserializerOptions(object_elements_trailing_comma=False) + self.f_opts = FormatterOptions(open_empty_objects=False) + + def run(self) -> None: + ret = self.exporter() + value = dumps( + self.dico, deserializer_options=self.d_opts, formatter_options=self.f_opts + ) + if DEBUG: + # You can compare to the result to `hcl2tojson xxxx.hcl`, it should be the same + print(add_quote(self.dico, ensure_ascii=False, indent=2)) + return ret, value + + def parse_sequence( + self, + child: "Option", + child_obj: list[dict], + ) -> None: + super().parse_sequence(child, child_obj) + for sequence in child_obj: + for key, value in sequence.items(): + sequence[key] = self.add_quote(child, value) + + def variable_informations(self, parent_obj, child: "Option") -> None: + if isinstance(parent_obj, list): + # the parent is a block + if not parent_obj: + parent_obj.append({}) + parent_obj = parent_obj[-1] + parent_obj[child.name()] = self.add_quote(child, child.value.get()) + + def add_quote(self, child: "Option", value: Any) -> Any: + if child and not child.information.get("hcl_quoted_string", True): + return value + if isinstance(value, str): + return add_quote(value) + if isinstance(value, list): + return [ + self.add_quote(None, val) if isinstance(val, str) else val + for val in value + ] + return value + + def get_default_family(self, child: "Option") -> Union[list, dict]: + hcl_type = child.information.get("hcl_type", None) + if hcl_type in ["block", "block_with_label"]: + return list() + return dict() + + def family_informations( + self, + parent: "Option", + parent_obj: Union[list, dict], + child: "Option", + child_obj: Union[list, dict], + ) -> None: + quote_name = self.family_add_block_information(parent, child, child_obj) + name = self.get_family_name(child, quote_name) + parent_is_block = isinstance(parent_obj, list) + child_is_block = isinstance(child_obj, list) + if child_is_block: + if parent_is_block: + if not parent_obj: + parent_obj.append({name: []}) + elif name not in parent_obj[-1]: + parent_obj[-1][name] = [] + block_to_insert = parent_obj[-1][name] + else: + if name not in parent_obj: + parent_obj[name] = [] + block_to_insert = parent_obj[name] + block_to_insert.extend(child_obj) + elif parent_is_block: + parent_obj.append({name: child_obj}) + else: + parent_obj[name] = child_obj + + def family_add_block_information( + self, parent: "Option", child: "Option", child_obj: Union[list, dict] + ) -> bool: + hcl_type = child.information.get("hcl_type", None) + quote_name = False + if hcl_type: + if self.config != parent and not parent.information.get("hcl_type", None): + msg = _( + 'The family "{0}" has a hcl_type parameter but is not in a root config' + ).format(child.path()) + raise DictConsistencyError( + msg, 500, child.information.get("ymlfiles", None) + ) + if hcl_type == "block": + child_obj[-1]["__is_block__"] = True + elif hcl_type == "label": + if self.config == parent or not parent.information.get( + "hcl_type", None + ): + msg = _( + 'The family "{0}" has a hcl_type parameter to label but is not in a block' + ).format(child.path()) + raise DictConsistencyError( + msg, 501, child.information.get("ymlfiles", None) + ) + child_obj["__is_block__"] = True + quote_name = True + elif hcl_type == "label_with_label": + quote_name = True + return quote_name + + def get_family_name(self, child: "Option", quote_name: bool) -> str: + name = child.information.get("hcl_name", None) + if not name: + name = child.name() + if quote_name: + return add_quote(name) + return name + + +__all__ = ("RougailOutputHcl",) diff --git a/src/rougail/output_hcl/i18n.py b/src/rougail/output_hcl/i18n.py new file mode 100644 index 0000000..65de1ab --- /dev/null +++ b/src/rougail/output_hcl/i18n.py @@ -0,0 +1,26 @@ +"""Internationalisation utilities +Silique (https://www.silique.fr) +Copyright (C) 2026 + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +""" + +from gettext import translation +from pathlib import Path + +t = translation( + "rougail_output_hcl", str(Path(__file__).parent / "locale"), fallback=True +) + +_ = t.gettext diff --git a/src/rougail/output_hcl/locale/fr/LC_MESSAGES/rougail_output_hcl.mo b/src/rougail/output_hcl/locale/fr/LC_MESSAGES/rougail_output_hcl.mo new file mode 100644 index 0000000000000000000000000000000000000000..070414c012c39081fc2c6c3b856e7529cb8e5724 GIT binary patch literal 681 zcmb7>%Syvg5QgIgk+^g1VsPU^PED&;jkrhAU=kBm%fDy z-@`W%oM@qfE_C4I&m@!oO!B|YPCj~46T+OZBFqR)p{zThAr!)a@FKhm(_^035}g|N zyc5w2QMvW$Bl2s>$f(*qf-d%TTEP5$@GuWO%mJYnrB_^J1Q|N)6O)6^fI;VAtcZ~j zXXPrgrmGY0?=Ea1#SW!E>^iB9ZoHPW*A%fjjMX{0Tw4=@-#S*ObgRow?IKeMwV+m4 z!I}!{P_2h6Yl}fG2>d4ItlADuo+4W(Z#K~+158M@3H#sr5W3DkIy`EOPN`M`f5#do zBOdbQAu!$XQkJ4Nn-KNT<-}WO?LD. +""" + +from .object_model import Family, Variable + + +__all__ = ("Family", "Variable") diff --git a/src/rougail/structural_hcl/annotator.py b/src/rougail/structural_hcl/annotator.py new file mode 100644 index 0000000..dd69b0b --- /dev/null +++ b/src/rougail/structural_hcl/annotator.py @@ -0,0 +1,82 @@ +""" +Silique (https://www.silique.fr) +Copyright (C) 2026 + +distribued with GPL-2 or later license + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +from rougail.annotator.variable import Walk +from rougail.error import DictConsistencyError + + +class Annotator(Walk): + """Annotate for bitwarden""" + + level = 92 + + def __init__( + self, + objectspace, + *args, # pylint: disable=unused-argument + ) -> None: + if not objectspace.paths: + return + self.objectspace = objectspace + self.parse_family() + self.parse_variable() + + def parse_family(self, current=".", parent_is_block=False): + is_label = None + for child_path in self.objectspace.parents[current]: + if child_path not in self.objectspace.families: + continue + child = self.objectspace.paths[child_path] + sub_is_block = is_block = ( + hasattr(child, "hcl_type") and child.hcl_type == "block" + ) + information_value = None + if child.hcl_type == "label": + if is_label == False: + raise Exception() + information_value = "label" + is_label = True + sub_is_block = True + elif is_label: + raise Exception() + else: + is_label = False + if self.parse_family(child_path, sub_is_block): + if is_label: + information_value = "label_with_label" + else: + information_value = "block_with_label" + elif is_block: + information_value = "block" + if information_value: + self.objectspace.informations.add( + child.path, "hcl_type", information_value + ) + hcl_name = getattr(child, "hcl_name") + if hcl_name: + self.objectspace.informations.add(child.path, "hcl_name", hcl_name) + return is_label + + def parse_variable(self): + for child in self.get_variables(): + if getattr(child, "hcl_quoted_string", True): + continue + self.objectspace.informations.add(child.path, "hcl_quoted_string", False) diff --git a/src/rougail/structural_hcl/config.py b/src/rougail/structural_hcl/config.py new file mode 100644 index 0000000..e4721a8 --- /dev/null +++ b/src/rougail/structural_hcl/config.py @@ -0,0 +1,44 @@ +""" +Silique (https://www.silique.fr) +Copyright (C) 2026 + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . +""" + + +def get_rougail_config( + *, + backward_compatibility: bool = True, # pylint: disable=unused-argument +) -> dict: + options = """ +step: + + structural: + redefine: true + default: + jinja: |- + {% if _.output is not propertyerror and _.output == 'hcl' %} + hcl + {% endif %} + directory +""" + return { + "name": "hcl", + "process": "structural", + "options": options, + "level": 40, + } + + +__all__ = ("get_rougail_config",) diff --git a/src/rougail/structural_hcl/object_model.py b/src/rougail/structural_hcl/object_model.py new file mode 100644 index 0000000..2c1ed10 --- /dev/null +++ b/src/rougail/structural_hcl/object_model.py @@ -0,0 +1,39 @@ +""" +Silique (https://www.silique.fr) +Copyright (C) 2026 + +distribued with GPL-2 or later license + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +from typing import Optional, Literal +from pydantic import BaseModel, StrictBool, StrictStr + + +class Family(BaseModel): + hcl_type: Literal[ + None, + "block", + "label", + ] = None + hcl_name: Optional[StrictStr] = None + + +class Variable(BaseModel): + hcl_quoted_string: StrictBool = True + + +__all__ = ("Family", "Variable") diff --git a/tests/errors/00-label_outside_block.yaml b/tests/errors/00-label_outside_block.yaml new file mode 100644 index 0000000..708e400 --- /dev/null +++ b/tests/errors/00-label_outside_block.yaml @@ -0,0 +1,9 @@ +%YAML 1.2 +--- +version: 1.1 + +a_label: + hcl_type: label + + a_variable: a value +... diff --git a/tests/errors/00-label_outside_block.yaml.errno b/tests/errors/00-label_outside_block.yaml.errno new file mode 100644 index 0000000..c15fb72 --- /dev/null +++ b/tests/errors/00-label_outside_block.yaml.errno @@ -0,0 +1 @@ +501 diff --git a/tests/results/00-doc_primitive.yaml.hcl b/tests/results/00-doc_primitive.yaml.hcl new file mode 100644 index 0000000..5637d37 --- /dev/null +++ b/tests/results/00-doc_primitive.yaml.hcl @@ -0,0 +1,4 @@ +a_string = "a value" +an_integer = 1 +a_boolean = true +null_value = null diff --git a/tests/results/01-doc_complex_types.yaml.hcl b/tests/results/01-doc_complex_types.yaml.hcl new file mode 100644 index 0000000..f9d79a3 --- /dev/null +++ b/tests/results/01-doc_complex_types.yaml.hcl @@ -0,0 +1,8 @@ +a_list = [ + "value 1", + "value 2", +] +an_integer_list = [ + 1, + 2, +] diff --git a/tests/results/01-doc_complex_types2.yaml.hcl b/tests/results/01-doc_complex_types2.yaml.hcl new file mode 100644 index 0000000..c564d6a --- /dev/null +++ b/tests/results/01-doc_complex_types2.yaml.hcl @@ -0,0 +1,10 @@ +a_map = { + key_1 = "value 1" + key_2 = "value 2" + key_3 = "value 3" +} +an_object = { + a_string = "a value" + an_integer = 1 + a_boolean = true +} diff --git a/tests/results/02-doc_expressions_basic.yaml.hcl b/tests/results/02-doc_expressions_basic.yaml.hcl new file mode 100644 index 0000000..bac16fc --- /dev/null +++ b/tests/results/02-doc_expressions_basic.yaml.hcl @@ -0,0 +1,3 @@ +a_first_string = "a value" +a_second_string = a_first_string +a_third_string = "combine ${a_first_string}" diff --git a/tests/results/03-doc_conditional_expressions.yaml.hcl b/tests/results/03-doc_conditional_expressions.yaml.hcl new file mode 100644 index 0000000..c56c5e1 --- /dev/null +++ b/tests/results/03-doc_conditional_expressions.yaml.hcl @@ -0,0 +1,2 @@ +a_first_string = "a value" +a_second_string = a_first_string == "a value" ? 2 : 1 diff --git a/tests/results/04-doc_basic_block.yaml.hcl b/tests/results/04-doc_basic_block.yaml.hcl new file mode 100644 index 0000000..1b068e4 --- /dev/null +++ b/tests/results/04-doc_basic_block.yaml.hcl @@ -0,0 +1,3 @@ +a_block { + an_attribute = "a value" +} diff --git a/tests/results/05-doc_block_in_block.yaml.hcl b/tests/results/05-doc_block_in_block.yaml.hcl new file mode 100644 index 0000000..5b16bf1 --- /dev/null +++ b/tests/results/05-doc_block_in_block.yaml.hcl @@ -0,0 +1,5 @@ +a_block { + a_second_block { + an_attribute = "a value" + } +} diff --git a/tests/results/06-doc_block_with_a_label.yaml.hcl b/tests/results/06-doc_block_with_a_label.yaml.hcl new file mode 100644 index 0000000..57171b3 --- /dev/null +++ b/tests/results/06-doc_block_with_a_label.yaml.hcl @@ -0,0 +1,3 @@ +a_block "a_label" { + an_attribute = "a value" +} diff --git a/tests/results/07-doc_block_with_some_labels.yaml.hcl b/tests/results/07-doc_block_with_some_labels.yaml.hcl new file mode 100644 index 0000000..c2849ef --- /dev/null +++ b/tests/results/07-doc_block_with_some_labels.yaml.hcl @@ -0,0 +1,3 @@ +a_block "a_first_label" "a_second_label" { + an_attribute = "a value" +} diff --git a/tests/results/08-doc_block_inside_a_label_block.yaml.hcl b/tests/results/08-doc_block_inside_a_label_block.yaml.hcl new file mode 100644 index 0000000..191a00a --- /dev/null +++ b/tests/results/08-doc_block_inside_a_label_block.yaml.hcl @@ -0,0 +1,5 @@ +a_block "a_first_label" "a_second_label" { + a_sub_block { + a_string = "a value" + } +} diff --git a/tests/results/09-doc_block_with_a_label_inside_a_block.yaml.hcl b/tests/results/09-doc_block_with_a_label_inside_a_block.yaml.hcl new file mode 100644 index 0000000..403c191 --- /dev/null +++ b/tests/results/09-doc_block_with_a_label_inside_a_block.yaml.hcl @@ -0,0 +1,5 @@ +a_block { + a_sub_block "a_first_label" "a_second_label" { + a_string = "a value" + } +} diff --git a/tests/results/09-doc_block_with_a_label_inside_a_block_multi.yaml.hcl b/tests/results/09-doc_block_with_a_label_inside_a_block_multi.yaml.hcl new file mode 100644 index 0000000..91a469d --- /dev/null +++ b/tests/results/09-doc_block_with_a_label_inside_a_block_multi.yaml.hcl @@ -0,0 +1,15 @@ +a_block { + a_sub_block "a_label" { + an_attribut = [ + "value 1", + "value2", + ] + } + + + a_sub_block "a_label" { + an_attribut = [ + "value 3", + ] + } +} diff --git a/tests/results/09-doc_variable_block.yaml.hcl b/tests/results/09-doc_variable_block.yaml.hcl new file mode 100644 index 0000000..087e763 --- /dev/null +++ b/tests/results/09-doc_variable_block.yaml.hcl @@ -0,0 +1,4 @@ +variable "environment" { + description = "The deployment environment" + type = string +} diff --git a/tests/results/91-block_with_a_label_inside_a_block_multi.yaml.hcl b/tests/results/91-block_with_a_label_inside_a_block_multi.yaml.hcl new file mode 100644 index 0000000..7813c1b --- /dev/null +++ b/tests/results/91-block_with_a_label_inside_a_block_multi.yaml.hcl @@ -0,0 +1,10 @@ +a_block { + a_sub_block_1 "a_first_label" "a_second_label" { + a_string = "a value" + } + + + a_sub_block_2 { + a_string = "a value" + } +} diff --git a/tests/results/92-examples.yaml.hcl b/tests/results/92-examples.yaml.hcl new file mode 100644 index 0000000..b26eb7a --- /dev/null +++ b/tests/results/92-examples.yaml.hcl @@ -0,0 +1,24 @@ +build { + name = "learn-packer" + sources = [ + "source.docker.ubuntu", + "source.docker.ubuntu-focal", + ] + + provisioner "shell" { + environment_vars = [ + "FOO=hello world", + ] + inline = [ + "echo Adding file to Docker Container", + "echo \"FOO is $FOO\" > example.txt", + ] + } + + + provisioner "shell" { + inline = [ + "echo Running ${var.docker_image} Docker image.", + ] + } +} diff --git a/tests/results/93-examples.yaml.hcl b/tests/results/93-examples.yaml.hcl new file mode 100644 index 0000000..dc58baf --- /dev/null +++ b/tests/results/93-examples.yaml.hcl @@ -0,0 +1,16 @@ +provisioner "shell" { + environment_vars = [ + "FOO=hello world", + ] + inline = [ + "echo Adding file to Docker Container", + "echo \"FOO is $FOO\" > example.txt", + ] +} + + +provisioner "shell" { + inline = [ + "echo Running ${var.docker_image} Docker image.", + ] +} diff --git a/tests/results/94-examples.yaml.hcl b/tests/results/94-examples.yaml.hcl new file mode 100644 index 0000000..b26eb7a --- /dev/null +++ b/tests/results/94-examples.yaml.hcl @@ -0,0 +1,24 @@ +build { + name = "learn-packer" + sources = [ + "source.docker.ubuntu", + "source.docker.ubuntu-focal", + ] + + provisioner "shell" { + environment_vars = [ + "FOO=hello world", + ] + inline = [ + "echo Adding file to Docker Container", + "echo \"FOO is $FOO\" > example.txt", + ] + } + + + provisioner "shell" { + inline = [ + "echo Running ${var.docker_image} Docker image.", + ] + } +} diff --git a/tests/results_subconfig/rougail.hcl b/tests/results_subconfig/rougail.hcl new file mode 100644 index 0000000..3761684 --- /dev/null +++ b/tests/results_subconfig/rougail.hcl @@ -0,0 +1,3 @@ +a_bock "a_label" { + a_variable = "a value" +} diff --git a/tests/structures/00-doc_primitive.yaml b/tests/structures/00-doc_primitive.yaml new file mode 100644 index 0000000..6496415 --- /dev/null +++ b/tests/structures/00-doc_primitive.yaml @@ -0,0 +1,14 @@ +%YAML 1.2 +--- +version: 1.1 + +a_string: a value # An attribute with a string + +an_integer: 1 # An attribute with a integer + +a_boolean: true # A boolean attribute + +null_value: + description: A nullable value + mandatory: false +... diff --git a/tests/structures/01-doc_complex_types.yaml b/tests/structures/01-doc_complex_types.yaml new file mode 100644 index 0000000..d70361f --- /dev/null +++ b/tests/structures/01-doc_complex_types.yaml @@ -0,0 +1,8 @@ +%YAML 1.2 +--- +version: 1.1 + +a_list: ["value 1", "value 2"] # An attribute with a list of strings + +an_integer_list: [1, 2] # An attribute with a list of integer +... diff --git a/tests/structures/01-doc_complex_types2.yaml b/tests/structures/01-doc_complex_types2.yaml new file mode 100644 index 0000000..7d5c044 --- /dev/null +++ b/tests/structures/01-doc_complex_types2.yaml @@ -0,0 +1,20 @@ +%YAML 1.2 +--- +version: 1.1 + +a_map: # A map is simply a collection of variables of the same type + + key_1: value 1 # A first attribute + + key_2: value 2 # A second attribute + + key_3: value 3 # A third attribute + +an_object: # A object is just a family + + a_string: a value # An attribute with a string + + an_integer: 1 # An attribute with a integer + + a_boolean: true # A boolean attribute +... diff --git a/tests/structures/02-doc_expressions_basic.yaml b/tests/structures/02-doc_expressions_basic.yaml new file mode 100644 index 0000000..eab50ba --- /dev/null +++ b/tests/structures/02-doc_expressions_basic.yaml @@ -0,0 +1,12 @@ +%YAML 1.2 +--- +version: 1.1 + +a_first_string: "a value" # Take a first value + +a_second_string: + description: A variable could also use expression to get value + hcl_quoted_string: false + default: a_first_string + +a_third_string: combine ${a_first_string} # An expressions is just a string diff --git a/tests/structures/03-doc_conditional_expressions.yaml b/tests/structures/03-doc_conditional_expressions.yaml new file mode 100644 index 0000000..81636f6 --- /dev/null +++ b/tests/structures/03-doc_conditional_expressions.yaml @@ -0,0 +1,11 @@ +%YAML 1.2 +--- +version: 1.1 + +a_first_string: "a value" # Take a first value + +a_second_string: + description: An expressions is just a string + hcl_quoted_string: false + default: "a_first_string == \"a value\" ? 2 : 1" +... diff --git a/tests/structures/04-doc_basic_block.yaml b/tests/structures/04-doc_basic_block.yaml new file mode 100644 index 0000000..e8bad01 --- /dev/null +++ b/tests/structures/04-doc_basic_block.yaml @@ -0,0 +1,10 @@ +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + an_attribute: a value # An attribute in a block +... diff --git a/tests/structures/05-doc_block_in_block.yaml b/tests/structures/05-doc_block_in_block.yaml new file mode 100644 index 0000000..1e35c80 --- /dev/null +++ b/tests/structures/05-doc_block_in_block.yaml @@ -0,0 +1,14 @@ +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + a_second_block: + description: We can add a block inside a block + hcl_type: block + + an_attribute: a value # An attribute in a block +... diff --git a/tests/structures/06-doc_block_with_a_label.yaml b/tests/structures/06-doc_block_with_a_label.yaml new file mode 100644 index 0000000..9b72c1e --- /dev/null +++ b/tests/structures/06-doc_block_with_a_label.yaml @@ -0,0 +1,14 @@ +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + a_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + an_attribute: a value # An attribute in a block +... diff --git a/tests/structures/07-doc_block_with_some_labels.yaml b/tests/structures/07-doc_block_with_some_labels.yaml new file mode 100644 index 0000000..389cd20 --- /dev/null +++ b/tests/structures/07-doc_block_with_some_labels.yaml @@ -0,0 +1,18 @@ +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + a_first_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + a_second_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + an_attribute: a value # An attribute in a block +... diff --git a/tests/structures/08-doc_block_inside_a_label_block.yaml b/tests/structures/08-doc_block_inside_a_label_block.yaml new file mode 100644 index 0000000..1ecfcc2 --- /dev/null +++ b/tests/structures/08-doc_block_inside_a_label_block.yaml @@ -0,0 +1,22 @@ +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + a_first_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + a_second_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + a_sub_block: + description: A block insible label block + hcl_type: block + + a_string: a value # An attribute in a block +... diff --git a/tests/structures/09-doc_block_with_a_label_inside_a_block.yaml b/tests/structures/09-doc_block_with_a_label_inside_a_block.yaml new file mode 100644 index 0000000..758d8c6 --- /dev/null +++ b/tests/structures/09-doc_block_with_a_label_inside_a_block.yaml @@ -0,0 +1,22 @@ +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + a_sub_block: + description: A label block insible block + hcl_type: block + + a_first_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + a_second_label: + description: A label is also a family with hcl_type attribute + hcl_type: label + + a_string: a value # An attribute in a block +... diff --git a/tests/structures/09-doc_block_with_a_label_inside_a_block_multi.yaml b/tests/structures/09-doc_block_with_a_label_inside_a_block_multi.yaml new file mode 100644 index 0000000..2a27254 --- /dev/null +++ b/tests/structures/09-doc_block_with_a_label_inside_a_block_multi.yaml @@ -0,0 +1,32 @@ +%YAML 1.2 +--- +version: 1.1 + +a_block: + description: A block is a family with hcl_type attribute + hcl_type: block + + a_sub_block_1: + description: A label block insible block + hcl_type: block + hcl_name: a_sub_block + + a_label: + description: Add a label + hcl_type: label + + an_attribut: # An attribute + - value 1 + - value2 + + a_sub_block_2: + description: Add a label + hcl_type: block + hcl_name: a_sub_block + + a_label: + hcl_type: label + + an_attribut: # An attribute + - value 3 +... diff --git a/tests/structures/09-doc_variable_block.yaml b/tests/structures/09-doc_variable_block.yaml new file mode 100644 index 0000000..5d56307 --- /dev/null +++ b/tests/structures/09-doc_variable_block.yaml @@ -0,0 +1,19 @@ +%YAML 1.2 +--- +version: 1.1 + +variable: + description: Variable block + hcl_type: block + + environment: + _description: The deployment environment + hcl_type: label + + description: The deployment environment # The environment description + + type: + description: The type description + hcl_quoted_string: false + default: string +... diff --git a/tests/structures/92-examples.yaml b/tests/structures/92-examples.yaml new file mode 100644 index 0000000..0202740 --- /dev/null +++ b/tests/structures/92-examples.yaml @@ -0,0 +1,37 @@ +%YAML 1.2 +--- +version: 1.1 + +build: + hcl_type: block + + name: learn-packer + + sources: + - source.docker.ubuntu + - source.docker.ubuntu-focal + + provisioner_1: + hcl_type: block + hcl_name: provisioner + + shell: + hcl_type: label + + environment_vars: + - FOO=hello world + + inline: + - echo Adding file to Docker Container + - "echo \"FOO is $FOO\" > example.txt" + + provisioner_2: + hcl_type: block + hcl_name: provisioner + + shell: + hcl_type: label + + inline: + - echo Running ${var.docker_image} Docker image. +... diff --git a/tests/structures/93-examples.yaml b/tests/structures/93-examples.yaml new file mode 100644 index 0000000..d3b216e --- /dev/null +++ b/tests/structures/93-examples.yaml @@ -0,0 +1,28 @@ +%YAML 1.2 +--- +version: 1.1 + +provisioner_1: + hcl_type: block + hcl_name: provisioner + + shell: + hcl_type: label + + environment_vars: + - FOO=hello world + + inline: + - echo Adding file to Docker Container + - "echo \"FOO is $FOO\" > example.txt" + +provisioner_2: + hcl_type: block + hcl_name: provisioner + + shell: + hcl_type: label + + inline: + - echo Running ${var.docker_image} Docker image. +... diff --git a/tests/structures/94-examples.yaml b/tests/structures/94-examples.yaml new file mode 100644 index 0000000..cc5239b --- /dev/null +++ b/tests/structures/94-examples.yaml @@ -0,0 +1,34 @@ +%YAML 1.2 +--- +version: 1.1 + +build: + hcl_type: block + + name: learn-packer + + sources: + - source.docker.ubuntu + - source.docker.ubuntu-focal + + provisioner: + hcl_type: block + + shell_1: + hcl_type: label + hcl_name: shell + + environment_vars: + - FOO=hello world + + inline: + - echo Adding file to Docker Container + - "echo \"FOO is $FOO\" > example.txt" + + shell_2: + hcl_type: label + hcl_name: shell + + inline: + - echo Running ${var.docker_image} Docker image. +... diff --git a/tests/structures_subconfig/00-rougail.yaml b/tests/structures_subconfig/00-rougail.yaml new file mode 100644 index 0000000..93313c7 --- /dev/null +++ b/tests/structures_subconfig/00-rougail.yaml @@ -0,0 +1,21 @@ +%YAML 1.2 +--- +version: 1.1 + +a_variable: a value # A first variable + +a_family: # A family + + a_bock: + description: A block + hcl_type: block + + a_label: + description: A label + hcl_type: label + + a_variable: + description: A variable with a value + default: + variable: ____.a_variable +... diff --git a/tests/test_error.py b/tests/test_error.py new file mode 100644 index 0000000..c645646 --- /dev/null +++ b/tests/test_error.py @@ -0,0 +1,43 @@ +from pytest import fixture, raises +from pathlib import Path +from rougail import Rougail, RougailConfig +from rougail.output_hcl import RougailOutputHcl as RougailOutput +from rougail.error import DictConsistencyError + + +EXT = 'hcl' + + +structures = list((Path(__file__).parent / 'errors').glob("*.yaml")) +structures.sort() + + +def idfn(fixture_value): + return fixture_value.name + + +@fixture(scope="module", params=structures, ids=idfn) +def test_file(request): + return request.param + + +def _test_structural_files(test_file, ext): + rougailconfig = RougailConfig.copy() + ################################## + rougailconfig['step.output'] = 'hcl' + rougailconfig["main_namespace"] = None + rougailconfig["main_structural_directories"] = [str(test_file.absolute())] + ################################## + rougail = Rougail(rougailconfig) + config = rougail.run() + config.information.set("description_type", "path_and_description") + config.property.read_only() + with Path(str(test_file.absolute()) + ".errno").open() as fh: + error_no = int(fh.read().strip()) + with raises(DictConsistencyError) as err: + generated_output = RougailOutput(config, rougailconfig=rougailconfig).run()[1] + assert err.value.errno == error_no, f'expected errno: {error_no}, errno: {err.value.errno}, msg: {err}' + + +def test_structural_files_hcl(test_file): + _test_structural_files(test_file, EXT) diff --git a/tests/test_load.py b/tests/test_load.py new file mode 100644 index 0000000..6eb76db --- /dev/null +++ b/tests/test_load.py @@ -0,0 +1,48 @@ +from pytest import fixture # , raises +from pathlib import Path +from rougail import Rougail, RougailConfig +from rougail.output_hcl import RougailOutputHcl as RougailOutput + + + +EXT = 'hcl' + + +structures = list((Path(__file__).parent / 'structures').glob("*.yaml")) +structures.sort() + + +def idfn(fixture_value): + return fixture_value.name + + +@fixture(scope="module", params=structures, ids=idfn) +def test_file(request): + return request.param + + +def _test_structural_files(test_file, ext): + rougailconfig = RougailConfig.copy() + ################################## + rougailconfig['step.output'] = 'hcl' + rougailconfig["main_namespace"] = None + rougailconfig["main_structural_directories"] = [str(test_file.absolute())] + ################################## + rougail = Rougail(rougailconfig) + config = rougail.run() + config.information.set("description_type", "path_and_description") + config.property.read_only() + generated_output = RougailOutput(config, rougailconfig=rougailconfig).run()[1] + output_file = Path(__file__).parent / 'results' / (test_file.name + "." + ext) + if not output_file.is_file(): + if not output_file.parent.is_dir(): + output_file.parent.mkdir() + with output_file.open('w') as outfh: + outfh.write(generated_output) + with output_file.open() as outfh: + attented_output = outfh.read() + assert generated_output == attented_output, f'filename {output_file}' + + +def test_structural_files_hcl(test_file): + _test_structural_files(test_file, EXT) diff --git a/tests/test_subconfig.py b/tests/test_subconfig.py new file mode 100644 index 0000000..815eb33 --- /dev/null +++ b/tests/test_subconfig.py @@ -0,0 +1,51 @@ +from pytest import raises +from pathlib import Path +from rougail import Rougail, RougailConfig +from rougail.output_hcl import RougailOutputHcl as RougailOutput +from rougail.error import DictConsistencyError + + + +def _test_subconfig(error): + rougailconfig = RougailConfig.copy() + ################################## + rougailconfig['step.output'] = 'hcl' + rougailconfig["main_namespace"] = None + rougailconfig["main_structural_directories"] = [str((Path(__file__).parent / 'structures_subconfig').absolute())] + ################################## + rougail = Rougail(rougailconfig) + config = rougail.run() + config.information.set("description_type", "path_and_description") + config.property.read_only() + if not error: + subconfig = config + elif error == "block": + subconfig = config.option("a_family") + else: + subconfig = config.option("a_family.a_bock") + generated_output = RougailOutput(subconfig, rougailconfig=rougailconfig, root_config=config, true_config=config).run()[1] + output_file = Path(__file__).parent / 'results_subconfig' / 'rougail.hcl' + if not output_file.is_file(): + if not output_file.parent.is_dir(): + output_file.parent.mkdir() + with output_file.open('w') as outfh: + outfh.write(generated_output) + with output_file.open() as outfh: + attented_output = outfh.read() + assert generated_output == attented_output, f'filename {output_file}' + + +def test_subconfig_error(): + with raises(DictConsistencyError) as err: + _test_subconfig(None) + assert err.value.errno == 500 + + +def test_subconfig(): + _test_subconfig("block") + + +def test_subconfig_error_label(): + with raises(DictConsistencyError) as err: + _test_subconfig("label") + assert err.value.errno == 501