Compare commits

..

3 commits

Author SHA1 Message Date
a51cfb8bfd bump: version 1.0.0rc1 → 1.0.0 2026-06-21 18:22:40 +02:00
575c9fb642 fix: rougail dependencies 2026-06-21 18:20:28 +02:00
dc52ad6ad5 fix: black 2026-06-21 16:53:52 +02:00
8 changed files with 165 additions and 255 deletions

View file

@ -1,212 +1,53 @@
## 1.0.0rc1 (2026-06-21) ## 1.0.0 (2026-06-21)
### Feat ### Feat
- better customize vars documentation - better customize vars documentation
### Fix
- update french translation
## 0.2.0a27 (2026-06-15)
### Feat
- can change the directory where documentation are placed + remove empty vars/main.yml file + better tests - can change the directory where documentation are placed + remove empty vars/main.yml file + better tests
## 0.2.0a26 (2026-06-11)
### Feat
- leadership => sequence - leadership => sequence
## 0.2.0a25 (2026-05-04)
### Feat
- gen doc for ansible output - gen doc for ansible output
- add dot - add dot
### Fix
- update tests
## 0.2.0a24 (2026-01-21)
### Feat
- doc rougail integration - doc rougail integration
## 0.2.0a23 (2026-01-14)
### Fix
- yaml for all output format
## 0.2.0a22 (2026-01-14)
### Feat
- gen doc of ansible type - gen doc of ansible type
### Fix
- update tests
## 0.2.0a21 (2026-01-09)
### Feat
- add vars for an specified host - add vars for an specified host
## 0.2.0a20 (2025-12-30)
### Feat
- add doc - add doc
### Fix
- update tests
## 0.2.0a19 (2025-12-22)
### Feat
- can sort variable per namespace - can sort variable per namespace
## 0.2.0a18 (2025-11-21)
### Feat
- can remove namespace name in host vars - can remove namespace name in host vars
## 0.2.0a17 (2025-11-06)
### Feat
- add some tests - add some tests
## 0.2.0a16 (2025-10-10)
### Fix
- update tests
- tests
## 0.2.0a15 (2025-10-03)
### Fix
- ansible in config is a family
- tests for formatter
## 0.2.0a14 (2025-09-29)
### Feat
- remove json.read_write option - remove json.read_write option
- default value for a calculated variable with an unknown optional variable - default value for a calculated variable with an unknown optional variable
- update tests for integer type - update tests for integer type
## 0.2.0a13 (2025-09-22)
### Fix
- dictionary => structure
## 0.2.0a12 (2025-09-10)
### Fix
- all group is not necesary
## 0.2.0a11 (2025-09-08)
### Fix
- "all" group are now compose with host and we remove duplicated hostname
## 0.2.0a10 (2025-07-04)
### Feat
- add ansible.export_warnings option - add ansible.export_warnings option
## 0.2.0a9 (2025-06-18)
### Fix
- rougail separation
## 0.2.0a8 (2025-05-12)
### Fix
- update tests
- black
## 0.2.0a7 (2025-05-02)
### Fix
- do not force use_data usage
## 0.2.0a6 (2025-04-30)
### Fix
- remove negative_description support
- update tests
- better mandatory support in test
## 0.2.0a5 (2025-04-09)
### Fix
- version
## 0.2.0a4 (2025-04-01)
### Fix
- update tests
## 0.2.0a3 (2025-02-17)
### Fix
- translation
## 0.2.0a2 (2025-02-10)
### Feat
- output return status too - output return status too
## 0.2.0a1 (2025-01-02)
### Fix
- user data ansible is loaded before
## 0.2.0a0 (2025-01-02)
### Feat
- add namespace_is_hostname option - add namespace_is_hostname option
- add tests - add tests
## 0.1.1a2 (2024-11-29)
## 0.1.1a1 (2024-11-28)
### Fix ### Fix
- rougail dependencies
- black
- update french translation
- update tests
- yaml for all output format
- update tests
- update tests
- update tests
- tests
- ansible in config is a family
- tests for formatter
- dictionary => structure
- all group is not necesary
- "all" group are now compose with host and we remove duplicated hostname
- rougail separation
- update tests
- black
- do not force use_data usage
- remove negative_description support
- update tests
- better mandatory support in test
- version
- update tests
- translation
- user data ansible is loaded before
- better errors support - better errors support
## 0.1.1a0 (2024-11-27)
### Fix
- first commit - first commit

View file

@ -4,7 +4,7 @@ requires = ["flit_core >=3.8.0,<4"]
[project] [project]
name = "rougail.output_ansible" name = "rougail.output_ansible"
version = "1.0.0rc1" version = "1.0.0"
authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}] authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
readme = "README.md" readme = "README.md"
description = "Rougail output ansible" description = "Rougail output ansible"
@ -24,8 +24,8 @@ classifiers = [
] ]
dependencies = [ dependencies = [
"rougail >= 1.1,<2", "rougail >= 1.2.0,<2",
"rougail-output-json >= 0.1", "rougail-output-json >= 1.0.0,<2",
"ansible", "ansible",
] ]

View file

@ -15,6 +15,7 @@ details.
You should have received a copy of the GNU Lesser General Public License 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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
from .ansible import Inventory from .ansible import Inventory
from .__version__ import __version__ from .__version__ import __version__
@ -34,25 +35,31 @@ class RougailOutputAnsible:
self.kwargs = kwargs self.kwargs = kwargs
def run(self): def run(self):
output = self.rougailconfig['ansible.output'] output = self.rougailconfig["ansible.output"]
if output == 'doc': if output == "doc":
from .doc import Doc from .doc import Doc
data = Doc(self.config, rougailconfig=self.rougailconfig, **self.kwargs) data = Doc(self.config, rougailconfig=self.rougailconfig, **self.kwargs)
else: else:
data = Inventory(self.config, rougailconfig=self.rougailconfig, **self.kwargs) data = Inventory(
self.config, rougailconfig=self.rougailconfig, **self.kwargs
)
return data.run() return data.run()
def print(self): def print(self):
output = self.rougailconfig['ansible.output'] output = self.rougailconfig["ansible.output"]
if output == 'doc': if output == "doc":
from .doc import Doc from .doc import Doc
data = Doc(self.config, rougailconfig=self.rougailconfig, **self.kwargs) data = Doc(self.config, rougailconfig=self.rougailconfig, **self.kwargs)
else: else:
data = Inventory(self.config, rougailconfig=self.rougailconfig, **self.kwargs) data = Inventory(
self.config, rougailconfig=self.rougailconfig, **self.kwargs
)
return data.print() return data.print()
RougailOutput = RougailOutputAnsible RougailOutput = RougailOutputAnsible
__all__ = ("RougailOutputAnsible", '__version__') __all__ = ("RougailOutputAnsible", "__version__")

View file

@ -1 +1 @@
__version__ = "1.0.0rc1" __version__ = "1.0.0"

View file

@ -18,8 +18,10 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
""" """
from rougail.error import DictConsistencyError from rougail.error import DictConsistencyError
from rougail.output_doc.i18n import _ from rougail.output_doc.i18n import _
try: try:
from rougail.output_doc.annotator import Annotator as AnnotatorDoc from rougail.output_doc.annotator import Annotator as AnnotatorDoc
except: except:
@ -27,11 +29,18 @@ except:
if AnnotatorDoc: if AnnotatorDoc:
class Annotator(AnnotatorDoc): class Annotator(AnnotatorDoc):
level = 96 level = 96
def __init__(self, *args): def __init__(self, *args):
super().__init__(*args, force_default_value=True, force_display_unknown_optional_variable=True) super().__init__(
*args,
force_default_value=True,
force_display_unknown_optional_variable=True,
)
# host_namespace = self.objectspace.rougailconfig["ansible.inventory.host_namespace"] # host_namespace = self.objectspace.rougailconfig["ansible.inventory.host_namespace"]
# namespaces = self.objectspace.parents["."] # namespaces = self.objectspace.parents["."]
# if not namespaces: # if not namespaces:

View file

@ -38,7 +38,9 @@ class Inventory(RougailOutputJson):
def exporter(self) -> None: def exporter(self) -> None:
self.host_namespace = self.rougailconfig["ansible.host_namespace"] self.host_namespace = self.rougailconfig["ansible.host_namespace"]
self.no_namespace_in_vars = self.rougailconfig["ansible.inventory.no_namespace_in_vars"] self.no_namespace_in_vars = self.rougailconfig[
"ansible.inventory.no_namespace_in_vars"
]
self.export_warnings = self.rougailconfig["ansible.inventory.export_warnings"] self.export_warnings = self.rougailconfig["ansible.inventory.export_warnings"]
self.hosts = {} self.hosts = {}
super().exporter() super().exporter()
@ -47,7 +49,11 @@ class Inventory(RougailOutputJson):
return True return True
def parse_variable(self, option, child, namespace): def parse_variable(self, option, child, namespace):
if self.support_namespace and namespace and "ansible_host" in option.information.get("tags", tuple()): if (
self.support_namespace
and namespace
and "ansible_host" in option.information.get("tags", tuple())
):
hosts = option.value.get() hosts = option.value.get()
if not isinstance(hosts, list): if not isinstance(hosts, list):
hosts = [hosts] hosts = [hosts]
@ -56,6 +62,7 @@ class Inventory(RougailOutputJson):
else: else:
self.hosts[namespace] = hosts self.hosts[namespace] = hosts
super().parse_variable(option, child, namespace) super().parse_variable(option, child, namespace)
# #
# def manage_errors(self) -> bool: # def manage_errors(self) -> bool:
# if not super().manage_errors(): # if not super().manage_errors():
@ -135,6 +142,7 @@ class Inventory(RougailOutputJson):
ret["_meta"]["hostvars"][host] = {"ansible_host": domain_name} ret["_meta"]["hostvars"][host] = {"ansible_host": domain_name}
if self.no_namespace_in_vars: if self.no_namespace_in_vars:
from rougail.tiramisu import normalize_family from rougail.tiramisu import normalize_family
host_namespace = normalize_family(host) host_namespace = normalize_family(host)
if host_namespace in self.dico: if host_namespace in self.dico:
ret["_meta"]["hostvars"][host].update( ret["_meta"]["hostvars"][host].update(
@ -144,7 +152,9 @@ class Inventory(RougailOutputJson):
if host in namespaces: if host in namespaces:
for namespace in namespaces[host]: for namespace in namespaces[host]:
if namespace in self.dico: if namespace in self.dico:
ret["_meta"]["hostvars"][host][namespace] = self.dico[namespace] ret["_meta"]["hostvars"][host][namespace] = (
self.dico[namespace]
)
else: else:
ret["_meta"]["hostvars"][host].update(self.dico) ret["_meta"]["hostvars"][host].update(self.dico)
if host in extra_vars: if host in extra_vars:

View file

@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from .i18n import _ from .i18n import _
from pathlib import Path from pathlib import Path
try: try:
from ..output_doc.config import OutPuts from ..output_doc.config import OutPuts
except: except:
@ -75,9 +76,12 @@ ansible:
default: inventory default: inventory
choices: choices:
- inventory - inventory
"""] """,
]
if doc_outputs: if doc_outputs:
options[-1] += f""" options[
-1
] += f"""
- doc - doc
host_namespace: hosts # {_('Namespace with host values')} host_namespace: hosts # {_('Namespace with host values')}
@ -99,7 +103,9 @@ ansible:
""" """
for output in doc_outputs: for output in doc_outputs:
options[-1] += f" - {output}\n" options[-1] += f" - {output}\n"
options[-1] += f""" options[
-1
] += f"""
collection_type: collection_type:
description: {_('collection contents')} description: {_('collection contents')}
choices: choices:

View file

@ -42,7 +42,8 @@ class Doc(RougailOutputDoc):
) -> None: ) -> None:
self.config = config self.config = config
self.ori_rougailconfig = rougailconfig self.ori_rougailconfig = rougailconfig
self.doc_rougailconfig = {"doc.tabulars.with_commandline": False, self.doc_rougailconfig = {
"doc.tabulars.with_commandline": False,
"doc.tabulars.with_environment": False, "doc.tabulars.with_environment": False,
"main_namespace": None, "main_namespace": None,
"step.output": "ansible", "step.output": "ansible",
@ -63,7 +64,11 @@ class Doc(RougailOutputDoc):
super().__init__(config, rougailconfig=self.doc_rougailconfig, **kwargs) super().__init__(config, rougailconfig=self.doc_rougailconfig, **kwargs)
def run(self) -> str: def run(self) -> str:
types = [config.name() for config in self.config if config.name() != self.ori_rougailconfig["ansible.host_namespace"]] types = [
config.name()
for config in self.config
if config.name() != self.ori_rougailconfig["ansible.host_namespace"]
]
self.root_config = self.config self.root_config = self.config
self.load() self.load()
self.load_formatter() self.load_formatter()
@ -78,37 +83,51 @@ class Doc(RougailOutputDoc):
config.property.read_only() config.property.read_only()
hidden = CommentedMap() hidden = CommentedMap()
config.property.remove("mandatory") config.property.remove("mandatory")
self._example_parse_family(config.value.get(), hidden, dump_type="hidden", with_true_path=True) self._example_parse_family(
config.value.get(), hidden, dump_type="hidden", with_true_path=True
)
config.property.read_write() config.property.read_write()
self.ansible_name = self.ori_rougailconfig["ansible.doc.project_name"] self.ansible_name = self.ori_rougailconfig["ansible.doc.project_name"]
self._build_defaults(config) self._build_defaults(config)
# FIXME self._build_vars(examples) # FIXME self._build_vars(examples)
# Description # Description
author = self.ori_rougailconfig["ansible.doc.author"] author = self.ori_rougailconfig["ansible.doc.author"]
name = f'{author}.{self.ansible_name}' name = f"{author}.{self.ansible_name}"
help_ = None help_ = None
description = None description = None
if self.ansible_name in self.informations: if self.ansible_name in self.informations:
if "help" in self.informations[self.ansible_name]["informations"]: if "help" in self.informations[self.ansible_name]["informations"]:
help_ = self.informations[self.ansible_name]["informations"].pop("help") help_ = self.informations[self.ansible_name]["informations"].pop("help")
if "description" in self.informations[self.ansible_name]["informations"]: if "description" in self.informations[self.ansible_name]["informations"]:
description = self.informations[self.ansible_name]["informations"]["description"] description = self.informations[self.ansible_name]["informations"][
"description"
]
title = f"{name} - {description}" title = f"{name} - {description}"
else: else:
title = name title = name
else: else:
title = name title = name
datas = [self.formatter.title(title, 1, collapse=False)] datas = [self.formatter.title(title, 1, collapse=False)]
datas.append(_('This repository contains the {0} Ansible collection.').format(self.formatter.prop(name, False, False, False))) datas.append(
_("This repository contains the {0} Ansible collection.").format(
self.formatter.prop(name, False, False, False)
)
)
if help_: if help_:
datas.append("\n".join(help_)) datas.append("\n".join(help_))
self.doc_rougailconfig["doc.title_level"] = 2 self.doc_rougailconfig["doc.title_level"] = 2
for type_ in self.informations: for type_ in self.informations:
current_informations = self.informations[type_]["informations"] current_informations = self.informations[type_]["informations"]
if "description" in current_informations: if "description" in current_informations:
current_informations["description"] = _('The group variables "{0}" - {1}').format(current_informations["name"], current_informations["description"]) current_informations["description"] = _(
'The group variables "{0}" - {1}'
).format(
current_informations["name"], current_informations["description"]
)
else: else:
current_informations["description"] = _("The group variables {0}").format(current_informations["name"]) current_informations["description"] = _(
"The group variables {0}"
).format(current_informations["name"])
datas.append(self.formatter.title(_("Variables"), 2, collapse=False)) datas.append(self.formatter.title(_("Variables"), 2, collapse=False))
self.formatter.options() self.formatter.options()
datas.extend(self.formatter.dict_to_dict(self.informations, level=3)) datas.extend(self.formatter.dict_to_dict(self.informations, level=3))
@ -128,7 +147,7 @@ class Doc(RougailOutputDoc):
found = False found = False
if not found: if not found:
continue continue
y["vars"][t] = f'{{{{ my_{t} }}}}' y["vars"][t] = f"{{{{ my_{t} }}}}"
# #
rougail_version = float(SUPPORTED_VERSION[-1]) rougail_version = float(SUPPORTED_VERSION[-1])
if "version" in types: if "version" in types:
@ -137,7 +156,7 @@ class Doc(RougailOutputDoc):
key = "version" key = "version"
if hidden: if hidden:
msg = ("Hidden variables can only be modified within a structure file.") msg = "Hidden variables can only be modified within a structure file."
datas.append(self.formatter.display_family_informations([msg])) datas.append(self.formatter.display_family_informations([msg]))
end_family = self.formatter.end_family(level=2, collapse=False) end_family = self.formatter.end_family(level=2, collapse=False)
if end_family: if end_family:
@ -146,7 +165,7 @@ class Doc(RougailOutputDoc):
datas.append(self.formatter.title(_("Example Playbook with Rougail"), 3)) datas.append(self.formatter.title(_("Example Playbook with Rougail"), 3))
structural = {key: rougail_version} structural = {key: rougail_version}
for t in types: for t in types:
structural[f'my_{t}'] = {'type': t} structural[f"my_{t}"] = {"type": t}
if structural: if structural:
customize = CommentedMap() customize = CommentedMap()
old_config = self.config old_config = self.config
@ -155,26 +174,34 @@ class Doc(RougailOutputDoc):
test_config.property.remove("mandatory") test_config.property.remove("mandatory")
test_config = test_config.forcepermissive test_config = test_config.forcepermissive
self.config = test_config self.config = test_config
self._example_parse_family(test_config.value.get(), customize, dump_type="empty", with_secret_manager=False, with_true_path=True) self._example_parse_family(
test_config.value.get(),
customize,
dump_type="empty",
with_secret_manager=False,
with_true_path=True,
)
self.config = old_config self.config = old_config
datas.append(_("Add to your structural file something like:")) datas.append(_("Add to your structural file something like:"))
datas.append(self.formatter.yaml(dump(structural), yaml_version="1.2")) datas.append(self.formatter.yaml(dump(structural), yaml_version="1.2"))
for t in types: for t in types:
if t in customize and t in hidden: if t in customize and t in hidden:
structural[f'my_{t}'].update(customize[t]) structural[f"my_{t}"].update(customize[t])
datas.append(_("Customizing hidden variables in structure file:")) datas.append(_("Customizing hidden variables in structure file:"))
datas.append(self.formatter.yaml(dump(structural), yaml_version="1.2")) datas.append(self.formatter.yaml(dump(structural), yaml_version="1.2"))
text = [_('Do not forget to add Rougail structure file as Rougail types.')] text = [_("Do not forget to add Rougail structure file as Rougail types.")]
datas.append(self.formatter.display_family_informations(text)) datas.append(self.formatter.display_family_informations(text))
user_data = {} user_data = {}
for t in types: for t in types:
if t in examples: if t in examples:
user_data.update({f'my_{t}': examples[t]}) user_data.update({f"my_{t}": examples[t]})
if user_data: if user_data:
datas.append(_("For example you can add an YAML user data with something like:")) datas.append(
_("For example you can add an YAML user data with something like:")
)
datas.append(self.formatter.yaml(dump(user_data))) datas.append(self.formatter.yaml(dump(user_data)))
datas.append(_("Add to your Play:")) datas.append(_("Add to your Play:"))
_dump = "\n".join([d[2:] for d in (" " + dump(yaml)).split('\n')]) _dump = "\n".join([d[2:] for d in (" " + dump(yaml)).split("\n")])
datas.append(self.formatter.yaml(_dump)) datas.append(self.formatter.yaml(_dump))
# #
examples = self._gen_doc_examples(config, True) examples = self._gen_doc_examples(config, True)
@ -189,12 +216,16 @@ class Doc(RougailOutputDoc):
for y in yaml: for y in yaml:
if not y["vars"]: if not y["vars"]:
del y["vars"][t] del y["vars"][t]
_dump = "\n".join([d[2:] for d in (" " + dump(yaml)).split('\n')]) _dump = "\n".join([d[2:] for d in (" " + dump(yaml)).split("\n")])
end_family = self.formatter.end_family(level=3) end_family = self.formatter.end_family(level=3)
if end_family: if end_family:
datas.append(end_family) datas.append(end_family)
datas.append(self.formatter.title(_("Example Playbook without Rougail"), 3)) datas.append(self.formatter.title(_("Example Playbook without Rougail"), 3))
datas.append(self.formatter.display_family_informations([_('The variables will not be properly validated without Rougail.')])) datas.append(
self.formatter.display_family_informations(
[_("The variables will not be properly validated without Rougail.")]
)
)
datas.append(self.formatter.yaml(_dump)) datas.append(self.formatter.yaml(_dump))
end_family = self.formatter.end_family(level=3) end_family = self.formatter.end_family(level=3)
if end_family: if end_family:
@ -224,7 +255,8 @@ class Doc(RougailOutputDoc):
playbook_description = playbooks[playbook_name] playbook_description = playbooks[playbook_name]
else: else:
playbook_description = f"Collection | {playbook_import}" playbook_description = f"Collection | {playbook_import}"
yaml = {"name": playbook_description, yaml = {
"name": playbook_description,
"hosts": "servers", "hosts": "servers",
"vars": {}, "vars": {},
} }
@ -233,7 +265,8 @@ class Doc(RougailOutputDoc):
if self.collection_type != "playbooks" and not done: if self.collection_type != "playbooks" and not done:
roles_dir = self.root_directory / "roles" roles_dir = self.root_directory / "roles"
if roles_dir.is_dir(): if roles_dir.is_dir():
yaml = {"name": description, yaml = {
"name": description,
"hosts": "servers", "hosts": "servers",
"vars": {}, "vars": {},
} }
@ -245,7 +278,11 @@ class Doc(RougailOutputDoc):
yaml["roles"].append({"role": f"{name}.{role_name}"}) yaml["roles"].append({"role": f"{name}.{role_name}"})
lst = [yaml] lst = [yaml]
if not done: if not done:
raise Exception(_('Unable to find a Playbook in playbooks/ directory or a role in roles/ directory')) raise Exception(
_(
"Unable to find a Playbook in playbooks/ directory or a role in roles/ directory"
)
)
return lst return lst
def _build_defaults(self, config): def _build_defaults(self, config):
@ -266,5 +303,5 @@ class Doc(RougailOutputDoc):
else: else:
filedir = self.root_directory / write_type filedir = self.root_directory / write_type
filedir.mkdir(exist_ok=True) filedir.mkdir(exist_ok=True)
with (filedir / "main.yml").open('w') as fh: with (filedir / "main.yml").open("w") as fh:
fh.write(f"---\n{dump(results)}\n") fh.write(f"---\n{dump(results)}\n")