feat: first version

This commit is contained in:
egarette@silique.fr 2026-07-06 19:29:28 +02:00
parent 101f0c67c6
commit 07e218cc01
54 changed files with 1669 additions and 2 deletions

503
README.md
View file

@ -1,3 +1,502 @@
# rougail-output-hcl
---
gitea: none
include_toc: true
---
Rougail output to HCL2 format
# 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
...
```

View file

@ -0,0 +1,31 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, 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"

View file

@ -0,0 +1,25 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

43
pyproject.toml Normal file
View file

@ -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

View file

@ -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 <http://www.gnu.org/licenses/>.
"""
from .export import RougailOutputHcl
from .__version__ import __version__
RougailOutput = RougailOutputHcl
__all__ = ("RougailOutputHcl", "__version__")

View file

@ -0,0 +1 @@
__version__ = "0.0.0"

View file

@ -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 <http://www.gnu.org/licenses/>.
"""
def get_rougail_config(
*,
backward_compatibility=True,
) -> dict:
return {
"name": "hcl",
"process": "output",
"level": 70,
}
__all__ = ("get_rougail_config",)

View file

@ -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 <http://www.gnu.org/licenses/>.
"""
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",)

View file

@ -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 <http://www.gnu.org/licenses/>.
"""
from gettext import translation
from pathlib import Path
t = translation(
"rougail_output_hcl", str(Path(__file__).parent / "locale"), fallback=True
)
_ = t.gettext

View file

@ -0,0 +1,22 @@
"""
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 <http://www.gnu.org/licenses/>.
"""
from .object_model import Family, Variable
__all__ = ("Family", "Variable")

View file

@ -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)

View file

@ -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 <http://www.gnu.org/licenses/>.
"""
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",)

View file

@ -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")

View file

@ -0,0 +1,9 @@
%YAML 1.2
---
version: 1.1
a_label:
hcl_type: label
a_variable: a value
...

View file

@ -0,0 +1 @@
501

View file

@ -0,0 +1,4 @@
a_string = "a value"
an_integer = 1
a_boolean = true
null_value = null

View file

@ -0,0 +1,8 @@
a_list = [
"value 1",
"value 2",
]
an_integer_list = [
1,
2,
]

View file

@ -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
}

View file

@ -0,0 +1,3 @@
a_first_string = "a value"
a_second_string = a_first_string
a_third_string = "combine ${a_first_string}"

View file

@ -0,0 +1,2 @@
a_first_string = "a value"
a_second_string = a_first_string == "a value" ? 2 : 1

View file

@ -0,0 +1,3 @@
a_block {
an_attribute = "a value"
}

View file

@ -0,0 +1,5 @@
a_block {
a_second_block {
an_attribute = "a value"
}
}

View file

@ -0,0 +1,3 @@
a_block "a_label" {
an_attribute = "a value"
}

View file

@ -0,0 +1,3 @@
a_block "a_first_label" "a_second_label" {
an_attribute = "a value"
}

View file

@ -0,0 +1,5 @@
a_block "a_first_label" "a_second_label" {
a_sub_block {
a_string = "a value"
}
}

View file

@ -0,0 +1,5 @@
a_block {
a_sub_block "a_first_label" "a_second_label" {
a_string = "a value"
}
}

View file

@ -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",
]
}
}

View file

@ -0,0 +1,4 @@
variable "environment" {
description = "The deployment environment"
type = string
}

View file

@ -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"
}
}

View file

@ -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.",
]
}
}

View file

@ -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.",
]
}

View file

@ -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.",
]
}
}

View file

@ -0,0 +1,3 @@
a_bock "a_label" {
a_variable = "a value"
}

View file

@ -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
...

View file

@ -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
...

View file

@ -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
...

View file

@ -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

View file

@ -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"
...

View file

@ -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
...

View file

@ -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
...

View file

@ -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
...

View file

@ -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
...

View file

@ -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
...

View file

@ -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
...

View file

@ -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
...

View file

@ -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
...

View file

@ -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.
...

View file

@ -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.
...

View file

@ -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.
...

View file

@ -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
...

43
tests/test_error.py Normal file
View file

@ -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)

48
tests/test_load.py Normal file
View file

@ -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)

51
tests/test_subconfig.py Normal file
View file

@ -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