From 1701d317d44baa0253546cf8582f863df408d96d Mon Sep 17 00:00:00 2001
From: Emmanuel Garette <egarette@silique.fr>
Date: Fri, 1 Nov 2024 11:17:14 +0100
Subject: [PATCH] feat: black + improvement

---
 pyproject.toml                            |  40 ++
 src/rougail/output_doc/__init__.py        | 535 +++++++++++++---------
 src/rougail/output_doc/annotator.py       | 220 +++++----
 src/rougail/output_doc/cli.py             |  36 --
 src/rougail/output_doc/config.py          |  66 +--
 src/rougail/output_doc/output/__init__.py |  28 +-
 src/rougail/output_doc/output/asciidoc.py | 125 +++--
 src/rougail/output_doc/output/github.py   | 119 +++--
 8 files changed, 691 insertions(+), 478 deletions(-)
 create mode 100644 pyproject.toml
 delete mode 100644 src/rougail/output_doc/cli.py

diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..89998d7
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,40 @@
+[build-system]
+build-backend = "flit_core.buildapi"
+requires = ["flit_core >=3.8.0,<4"]
+
+[project]
+name = "rougail.output_doc"
+version = "0.1.0rc0"
+authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
+readme = "README.md"
+description = "Rougail output doc"
+requires-python = ">=3.8"
+license = {file = "LICENSE"}
+classifiers = [
+    "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12",
+    "Programming Language :: Python :: 3",
+    "Operating System :: OS Independent",
+    "Natural Language :: English",
+    "Natural Language :: French",
+
+]
+dependencies = [
+    "rougail ~= 1.1.0",
+]
+
+[project.urls]
+Home = "https://forge.cloud.silique.fr/stove/rougail-output-exporter"
+
+[tool.commitizen]
+name = "cz_conventional_commits"
+tag_format = "$version"
+version_scheme = "pep440"
+version_provider = "pep621"
+update_changelog_on_bump = true
+changelog_merge_prerelease = true
diff --git a/src/rougail/output_doc/__init__.py b/src/rougail/output_doc/__init__.py
index 38c352c..ed6da20 100644
--- a/src/rougail/output_doc/__init__.py
+++ b/src/rougail/output_doc/__init__.py
@@ -1,25 +1,22 @@
 #!/usr/bin/env python3
 """
 Silique (https://www.silique.fr)
-Copyright (C) 2022-2024
+Copyright (C) 2024
             
-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
+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/>.
 """
-#FIXME si plusieurs example dont le 1er est none tester les autres : tests/dictionaries/00_8test_none
+# FIXME si plusieurs example dont le 1er est none tester les autres : tests/dictionaries/00_8test_none
 from tiramisu import Calculation
 from tiramisu.error import display_list
 import tabulate as tabulate_module
@@ -27,62 +24,62 @@ from tabulate import tabulate
 from warnings import warn
 from typing import Optional
 
-from gettext import gettext as _
-
 from rougail.error import display_xmlfiles
 from rougail import RougailConfig, Rougail, CONVERT_OPTION
 from rougail.object_model import PROPERTY_ATTRIBUTE
 
 from .config import OutPuts
+from .i18n import _
 
 ENTER = "\n\n"
 
 
 DocTypes = {
-    'domainname': {
-        'params': {
-            'allow_startswith_dot': _('the domain name can starts by a dot'),
-            'allow_without_dot': _('the domain name can be only a hostname'),
-            'allow_ip': _('the domain name can be an IP'),
-            'allow_cidr_network': _('the domain name can be network in CIDR format'),
+    "domainname": {
+        "params": {
+            "allow_startswith_dot": _("the domain name can starts by a dot"),
+            "allow_without_dot": _("the domain name can be a hostname"),
+            "allow_ip": _("the domain name can be an IP"),
+            "allow_cidr_network": _("the domain name can be network in CIDR format"),
         },
     },
-    'number': {
-        'params': {
-            'min_number': _('the minimum value is {value}'),
-            'max_number': _('the maximum value is {value}'),
+    "number": {
+        "params": {
+            "min_number": _("the minimum value is {value}"),
+            "max_number": _("the maximum value is {value}"),
         },
     },
-    'ip': {
-        'msg': 'IP',
-        'params': {
-            'cidr': _('IP must be in CIDR format'),
-            'private_only': _('private IP are allowed'),
-            'allow_reserved': _('reserved IP are allowed'),
+    "ip": {
+        "msg": "IP",
+        "params": {
+            "cidr": _("IP must be in CIDR format"),
+            "private_only": _("private IP are allowed"),
+            "allow_reserved": _("reserved IP are allowed"),
         },
     },
-    'hostname': {
-        'params': {
-            'allow_ip': _('the host name can be an IP'),
+    "hostname": {
+        "params": {
+            "allow_ip": _("the host name can be an IP"),
         },
     },
-    'web_address': {
-        'params': {
-            'allow_ip': _('the domain name in web address can be an IP'),
-            'allow_without_dot': _('the domain name in web address can be only a hostname'),
+    "web_address": {
+        "params": {
+            "allow_ip": _("the domain name in web address can be an IP"),
+            "allow_without_dot": _(
+                "the domain name in web address can be only a hostname"
+            ),
         },
     },
-    'port': {
-        'params': {
-            'allow_range': _('can be range of port'),
-            'allow_protocol': _('can have the protocol'),
-            'allow_zero': _('port 0 is allowed'),
-            'allow_wellknown': _('ports 1 to 1023 are allowed'),
-            'allow_registred': _('ports 1024 to 49151 are allowed'),
-            'allow_private': _('ports greater than 49152 are allowed'),
+    "port": {
+        "params": {
+            "allow_range": _("can be range of port"),
+            "allow_protocol": _("can have the protocol"),
+            "allow_zero": _("port 0 is allowed"),
+            "allow_wellknown": _("ports 1 to 1023 are allowed"),
+            "allow_registred": _("ports 1024 to 49151 are allowed"),
+            "allow_private": _("ports greater than 49152 are allowed"),
         },
     },
-
 }
 
 
@@ -92,36 +89,48 @@ ROUGAIL_VARIABLE_TYPE = (
 
 
 class RougailOutputDoc:
-    def __init__(self,
-                 *,
-                 config: 'Config'=None,
-                 rougailconfig: RougailConfig=None,
-                 ):
+    def __init__(
+        self,
+        *,
+        config: "Config" = None,
+        rougailconfig: RougailConfig = None,
+        **kwarg,
+    ):
         if rougailconfig is None:
             rougailconfig = RougailConfig
+            if rougailconfig["step.output"] != "doc":
+                rougailconfig["step.output"] = "doc"
+        if rougailconfig["step.output"] != "doc":
+            raise Exception("doc is not set as step.output")
         self.rougailconfig = rougailconfig
         outputs = OutPuts().get()
-        output = self.rougailconfig['doc.output_format']
+        output = self.rougailconfig["doc.output_format"]
         if output not in outputs:
-            raise Exception(f'cannot find output "{output}", available outputs: {list(outputs)}')
+            raise Exception(
+                f'cannot find output "{output}", available outputs: {list(outputs)}'
+            )
         if config is None:
             rougail = Rougail(self.rougailconfig)
-            rougail.converted.plugins.append('output_doc')
+            rougail.converted.plugins.append("output_doc")
             config = rougail.get_config()
         self.conf = config
-        self.conf.property.setdefault(frozenset({'advanced'}), 'read_write', 'append')
+        self.conf.property.setdefault(frozenset({"advanced"}), "read_write", "append")
         self.conf.property.read_write()
         self.conf.property.remove("cache")
         self.dynamic_paths = {}
         self.formater = outputs[output]()
-        self.level = self.rougailconfig['doc.title_level']
-        #self.property_to_string = [('mandatory', 'obligatoire'), ('hidden', 'cachée'), ('disabled', 'désactivée'), ('unique', 'unique'), ('force_store_value', 'modifié automatiquement')]
-        self.property_to_string = [('mandatory', _('mandatory')),
-                                   ('hidden', _('hidden')),
-                                   ('disabled', _('disabled')),
-                                   ('unique', _('unique')),
-                                   ('force_store_value', _('auto modified')),
-                                   ]
+        self.level = self.rougailconfig["doc.title_level"]
+        # self.property_to_string = [('mandatory', 'obligatoire'), ('hidden', 'cachée'), ('disabled', 'désactivée'), ('unique', 'unique'), ('force_store_value', 'modifié automatiquement')]
+        self.property_to_string = [
+            ("mandatory", _("mandatory")),
+            ("hidden", _("hidden")),
+            ("disabled", _("disabled")),
+            ("unique", _("unique")),
+            ("force_store_value", _("auto modified")),
+        ]
+
+    def run(self):
+        print(self.gen_doc())
 
     def gen_doc(self):
         tabulate_module.PRESERVE_WHITESPACE = True
@@ -133,51 +142,62 @@ class RougailOutputDoc:
                 name = namespace.name()
                 examples_mini[name] = {}
                 examples_all[name] = {}
-                doc = self._display_doc(
-                    self.display_families(
-                        namespace,
-                        self.level + 1,
-                        examples_mini[name],
-                        examples_all[name],
-                    ),
-                    [],
-                ) + '\n'
+                doc = (
+                    self._display_doc(
+                        self.display_families(
+                            namespace,
+                            self.level + 1,
+                            examples_mini[name],
+                            examples_all[name],
+                        ),
+                        [],
+                    )
+                    + "\n"
+                )
                 if not examples_mini[name]:
                     del examples_mini[name]
                 if not examples_all[name]:
                     del examples_all[name]
                 else:
-                    return_string += self.formater.title(_(f'Variables for "{namespace.name()}"'), self.level)
+                    return_string += self.formater.title(
+                        _(f'Variables for "{namespace.name()}"'), self.level
+                    )
                     return_string += doc
         else:
-            doc = self._display_doc(
-                self.display_families(
-                    self.conf.unrestraint,
-                    self.level + 1,
-                    examples_mini,
-                    examples_all,
-                ),
-                [],
-            ) + '\n'
+            doc = (
+                self._display_doc(
+                    self.display_families(
+                        self.conf.unrestraint,
+                        self.level + 1,
+                        examples_mini,
+                        examples_all,
+                    ),
+                    [],
+                )
+                + "\n"
+            )
             if examples_all:
-                return_string += self.formater.title(_(f'Variables'), self.level)
+                return_string += self.formater.title(_(f"Variables"), self.level)
                 return_string += doc
         if not examples_all:
-            return ''
-        if examples_mini:
-                #"Exemple avec les variables obligatoires non renseignées"
-            return_string += self.formater.title(
-                _("Example with mandatory variables not filled in"), self.level
-            )
-            return_string += self.formater.yaml(examples_mini)
-        if examples_all:
-            #"Exemple avec tous les variables modifiables"
-            return_string += self.formater.title("Example with all variables modifiable", self.level)
-            return_string += self.formater.yaml(examples_all)
+            return ""
+        if self.rougailconfig["doc.with_example"]:
+            if examples_mini:
+                # "Exemple avec les variables obligatoires non renseignées"
+                return_string += self.formater.title(
+                    _("Example with mandatory variables not filled in"), self.level
+                )
+                return_string += self.formater.yaml(examples_mini)
+            if examples_all:
+                # "Exemple avec tous les variables modifiables"
+                return_string += self.formater.title(
+                    "Example with all variables modifiable", self.level
+                )
+                return_string += self.formater.yaml(examples_all)
         return return_string
 
     def _display_doc(self, variables, add_paths):
-        return_string = ''
+        return_string = ""
         for variable in variables:
             typ = variable["type"]
             path = variable["path"]
@@ -189,21 +209,50 @@ class RougailOutputDoc:
             else:
                 for idx, path in enumerate(variable["paths"]):
                     if path in self.dynamic_paths:
-                        paths_msg = display_list([self.formater.bold(path_) for path_ in self.dynamic_paths[path]['paths']], separator='or')
-                        variable["objects"][idx][0] = variable["objects"][idx][0].replace('{{ ROUGAIL_PATH }}', paths_msg)
-                        suffixes = self.dynamic_paths[path]['suffixes']
+                        paths_msg = display_list(
+                            [
+                                self.formater.bold(path_)
+                                for path_ in self.dynamic_paths[path]["paths"]
+                            ],
+                            separator="or",
+                        )
+                        variable["objects"][idx][0] = variable["objects"][idx][
+                            0
+                        ].replace("{{ ROUGAIL_PATH }}", paths_msg)
+                        identifiers = self.dynamic_paths[path]["identifiers"]
                         description = variable["objects"][idx][1][0]
-                        if "{{ suffix }}" in description:
-                            if description.endswith('.'):
+                        if "{{ identifier }}" in description:
+                            if description.endswith("."):
                                 description = description[:-1]
-                            comment_msg = self.to_phrase(display_list([description.replace('{{ suffix }}', self.formater.italic(suffix)) for suffix in suffixes], separator='or', add_quote=True))
+                            comment_msg = self.to_phrase(
+                                display_list(
+                                    [
+                                        description.replace(
+                                            "{{ identifier }}",
+                                            self.formater.italic(identifier),
+                                        )
+                                        for identifier in identifiers
+                                    ],
+                                    separator="or",
+                                    add_quote=True,
+                                )
+                            )
                             variable["objects"][idx][1][0] = comment_msg
-                    variable["objects"][idx][1] = self.formater.join(variable["objects"][idx][1])
-                return_string += self.formater.table(tabulate(
-                    variable["objects"],
-                    headers=self.formater.table_header(['Variable', 'Description']),
-                    tablefmt=self.formater.name,
-                )) + '\n\n'
+                    variable["objects"][idx][1] = self.formater.join(
+                        variable["objects"][idx][1]
+                    )
+                return_string += (
+                    self.formater.table(
+                        tabulate(
+                            variable["objects"],
+                            headers=self.formater.table_header(
+                                ["Variable", "Description"]
+                            ),
+                            tablefmt=self.formater.name,
+                        )
+                    )
+                    + "\n\n"
+                )
             add_paths.append(path)
         return return_string
 
@@ -236,8 +285,12 @@ class RougailOutputDoc:
                     continue
                 path = child.path(uncalculated=True)
                 if child.isdynamic():
-                    self.dynamic_paths.setdefault(path, {'paths': [], 'suffixes': []})['paths'].append(child.path())
-                    self.dynamic_paths[path]['suffixes'].append(child.suffixes()[-1])
+                    self.dynamic_paths.setdefault(
+                        path, {"paths": [], "identifiers": []}
+                    )["paths"].append(child.path())
+                    self.dynamic_paths[path]["identifiers"].append(
+                        child.identifiers()[-1]
+                    )
                 if not variables or variables[-1]["type"] != "variables":
                     variables.append(
                         {
@@ -298,9 +351,18 @@ class RougailOutputDoc:
             title = f"{family.path()}"
         isdynamic = family.isdynamic(only_self=True)
         if isdynamic:
-            suffixes = family.suffixes(only_self=True)
-            if '{{ suffix }}' in title:
-                title = display_list([title.replace('{{ suffix }}', self.formater.italic(suffix)) for suffix in suffixes], separator='or', add_quote=True)
+            identifiers = family.identifiers(only_self=True)
+            if "{{ identifier }}" in title:
+                title = display_list(
+                    [
+                        title.replace(
+                            "{{ identifier }}", self.formater.italic(identifier)
+                        )
+                        for identifier in identifiers
+                    ],
+                    separator="or",
+                    add_quote=True,
+                )
         msg = self.formater.title(title, level)
         subparameter = []
         self.manage_properties(family, subparameter)
@@ -309,8 +371,8 @@ class RougailOutputDoc:
         comment = []
         self.subparameter_to_parameter(subparameter, comment)
         if comment:
-            msg += '\n'.join(comment) + ENTER
-        help = self.to_phrase(family.information.get('help', ""))
+            msg += "\n".join(comment) + ENTER
+        help = self.to_phrase(family.information.get("help", ""))
         if help:
             msg += "\n" + help + ENTER
         if family.isleadership():
@@ -318,39 +380,43 @@ class RougailOutputDoc:
             help = "This family contains lists of variable blocks."
             msg += "\n" + help + ENTER
         if isdynamic:
-            suffixes = family.suffixes(only_self=True , uncalculated=True)
-            if isinstance(suffixes, Calculation):
-                suffixes = self.to_string(family, 'dynamic')
-            if isinstance(suffixes, list):
-                for idx, val in enumerate(suffixes):
+            identifiers = family.identifiers(only_self=True, uncalculated=True)
+            if isinstance(identifiers, Calculation):
+                identifiers = self.to_string(family, "dynamic")
+            if isinstance(identifiers, list):
+                for idx, val in enumerate(identifiers):
                     if not isinstance(val, Calculation):
                         continue
-                    suffixes[idx] = self.to_string(family, 'dynamic', f'_{idx}')
-                suffixes = self.formater.list(suffixes)
-            #help = f"Cette famille construit des familles dynamiquement.\n\n{self.formater.bold('Suffixes')}: {suffixes}"
-            help = f"This family builds families dynamically.\n\n{self.formater.bold('Suffixes')}: {suffixes}"
+                    identifiers[idx] = self.to_string(family, "dynamic", f"_{idx}")
+                identifiers = self.formater.list(identifiers)
+            # help = f"Cette famille construit des familles dynamiquement.\n\n{self.formater.bold('Identifiers')}: {identifiers}"
+            help = f"This family builds families dynamically.\n\n{self.formater.bold('Identifiers')}: {identifiers}"
             msg += "\n" + help + ENTER
         return msg
 
-    def manage_properties(self,
-                          variable,
-                          subparameter,
-                          ):
+    def manage_properties(
+        self,
+        variable,
+        subparameter,
+    ):
         properties = variable.property.get(uncalculated=True)
-        for mode in self.rougailconfig['modes_level']:
+        for mode in self.rougailconfig["modes_level"]:
             if mode in properties:
                 subparameter.append((self.formater.prop(mode), None, None))
                 break
         for prop, msg in self.property_to_string:
             if prop in properties:
                 subparameter.append((self.formater.prop(msg), None, None))
-            elif variable.information.get(f'{prop}_calculation', False):
-                subparameter.append((self.formater.prop(msg), msg, self.to_string(variable, prop)))
+            elif variable.information.get(f"{prop}_calculation", False):
+                subparameter.append(
+                    (self.formater.prop(msg), msg, self.to_string(variable, prop))
+                )
 
-    def subparameter_to_string(self,
-                               subparameter,
-                               ):
-        subparameter_str = ''
+    def subparameter_to_string(
+        self,
+        subparameter,
+    ):
+        subparameter_str = ""
         for param in subparameter:
             if param[1]:
                 subparameter_str += f"_{param[0]}_ "
@@ -358,10 +424,11 @@ class RougailOutputDoc:
                 subparameter_str += f"{param[0]} "
         return subparameter_str[:-1]
 
-    def subparameter_to_parameter(self,
-                                  subparameter,
-                                  comment,
-                                  ):
+    def subparameter_to_parameter(
+        self,
+        subparameter,
+        comment,
+    ):
         for param in subparameter:
             if not param[1]:
                 continue
@@ -370,10 +437,10 @@ class RougailOutputDoc:
 
     def to_phrase(self, msg):
         if not msg:
-            return ''
+            return ""
         msg = str(msg).strip()
-        if not msg.endswith('.'):
-            msg += '.'
+        if not msg.endswith("."):
+            msg += "."
         return msg[0].upper() + msg[1:]
 
     def display_variable(
@@ -389,16 +456,18 @@ class RougailOutputDoc:
         subparameter = []
         description = variable.description(uncalculated=True)
         comment = [self.to_phrase(description)]
-        help_ = self.to_phrase(variable.information.get("help", ''))
+        help_ = self.to_phrase(variable.information.get("help", ""))
         if help_:
             comment.append(help_)
-        self.type_to_string(variable,
-                            subparameter,
-                            comment,
-                            )
-        self.manage_properties(variable,
-                               subparameter,
-                               )
+        self.type_to_string(
+            variable,
+            subparameter,
+            comment,
+        )
+        self.manage_properties(
+            variable,
+            subparameter,
+        )
         if variable.ismulti():
             multi = not variable.isfollower() or variable.issubmulti()
         else:
@@ -410,127 +479,161 @@ class RougailOutputDoc:
         if variable.name() == description:
             warning = f'No attribute "description" for variable "{variable.path()}" in {display_xmlfiles(variable.information.get("dictionaries"))}'
             warn(warning)
-        default = self.get_default(variable,
-                                   comment,
-                                   )
+        default = self.get_default(
+            variable,
+            comment,
+        )
         default_in_choices = False
-        if variable.information.get("type") == 'choice':
+        if variable.information.get("type") == "choice":
             choices = variable.value.list(uncalculated=True)
             if isinstance(choices, Calculation):
-                choices = self.to_string(variable, 'choice')
+                choices = self.to_string(variable, "choice")
             if isinstance(choices, list):
                 for idx, val in enumerate(choices):
                     if not isinstance(val, Calculation):
                         if default is not None and val == default:
-                            choices[idx] = str(val) + ' ← ' + _("(default)")
+                            choices[idx] = str(val) + " ← " + _("(default)")
                             default_in_choices = True
                         continue
-                    choices[idx] = self.to_string(variable, 'choice', f'_{idx}')
+                    choices[idx] = self.to_string(variable, "choice", f"_{idx}")
                 choices = self.formater.list(choices)
             comment.append(f'{self.formater.bold(_("Choices"))}: {choices}')
             # choice
         if default is not None and not default_in_choices:
             comment.append(f"{self.formater.bold(_('Default'))}: {default}")
-        self.manage_exemples(multi,
-                             variable,
-                             examples_all,
-                             examples_mini,
-                             comment,
-                             )
+        self.manage_exemples(
+            multi,
+            variable,
+            examples_all,
+            examples_mini,
+            comment,
+        )
         self.subparameter_to_parameter(subparameter, comment)
         self.formater.columns(parameter, comment)
         return [self.formater.join(parameter), comment]
 
-    def get_default(self,
-                    variable,
-                    comment,
-                    ):
-        if variable.information.get('fake_default', False):
+    def get_default(
+        self,
+        variable,
+        comment,
+    ):
+        if variable.information.get("fake_default", False):
             default = None
         else:
             default = variable.value.get(uncalculated=True)
         if default in [None, []]:
             return
         if isinstance(default, Calculation):
-            default = self.to_string(variable, 'default')
+            default = self.to_string(variable, "default")
         if isinstance(default, list):
             for idx, val in enumerate(default):
                 if not isinstance(val, Calculation):
                     continue
-                default[idx] = self.to_string(variable, 'default', f'_{idx}')
+                default[idx] = self.to_string(variable, "default", f"_{idx}")
             default = self.formater.list(default)
         return default
 
-    def to_string(self,
-                  variable,
-                  prop,
-                  suffix='',
-                  ):
-        calculation_type = variable.information.get(f'{prop}_calculation_type{suffix}', None)
+    def to_string(
+        self,
+        variable,
+        prop,
+        identifier="",
+    ):
+        calculation_type = variable.information.get(
+            f"{prop}_calculation_type{identifier}", None
+        )
         if not calculation_type:
-            raise Exception(f'cannot find {prop}_calculation_type{suffix} information, do you have declare doc has a plugins?')
-        calculation = variable.information.get(f'{prop}_calculation{suffix}')
-        if calculation_type == 'jinja':
+            raise Exception(
+                f"cannot find {prop}_calculation_type{identifier} information, do you have declare doc has a plugins?"
+            )
+        calculation = variable.information.get(f"{prop}_calculation{identifier}")
+        if calculation_type == "jinja":
             if calculation is not True:
                 values = self.formater.to_string(calculation)
             else:
-                values = "issu d'un calcul"
+                values = "depends on a calculation"
                 warning = f'"{prop}" is a calculation for {variable.path()} but has no description in {display_xmlfiles(variable.information.get("dictionaries"))}'
                 warn(warning)
-        elif calculation_type == 'variable':
+        elif calculation_type == "variable":
             if prop in PROPERTY_ATTRIBUTE:
                 values = self.formater.to_string(calculation)
             else:
                 values = _(f'the value of the variable "{calculation}"')
+        elif calculation_type == "identifier":
+            if prop in PROPERTY_ATTRIBUTE:
+                values = self.formater.to_string(calculation)
+            else:
+                values = _(f"value of the {calculation_type}")
         else:
             values = _(f"value of the {calculation_type}")
-        if not values.endswith('.'):
-            values += '.'
+        if not values.endswith("."):
+            values += "."
         return values
 
-    def type_to_string(self,
-                       variable,
-                       subparameter,
-                       comment,
-                       ):
+    def type_to_string(
+        self,
+        variable,
+        subparameter,
+        comment,
+    ):
         variable_type = variable.information.get("type")
-        doc_type = DocTypes.get(variable_type, {'params': {}})
-        subparameter.append((self.formater.link(doc_type.get('msg', variable_type), ROUGAIL_VARIABLE_TYPE), None))
+        doc_type = DocTypes.get(variable_type, {"params": {}})
+        subparameter.append(
+            (
+                self.formater.link(
+                    doc_type.get("msg", variable_type), ROUGAIL_VARIABLE_TYPE
+                ),
+                None,
+            )
+        )
         option = variable.get()
         validators = []
-        for param, msg in doc_type['params'].items():
-            value = option.impl_get_extra(f'_{param}')
+        for param, msg in doc_type["params"].items():
+            value = option.impl_get_extra(f"_{param}")
             if value is None:
                 value = option.impl_get_extra(param)
             if value is not None and value is not False:
                 validators.append(msg.format(value=value))
-        valids = [name for name in variable.information.list() if name.startswith('validators_calculation_type_')]
+        valids = [
+            name
+            for name in variable.information.list()
+            if name.startswith("validators_calculation_type_")
+        ]
         if valids:
             for idx in range(len(valids)):
-                validators.append(self.to_string(variable,
-                                                 'validators',
-                                                 f'_{idx}',
-                                                 ))
+                validators.append(
+                    self.to_string(
+                        variable,
+                        "validators",
+                        f"_{idx}",
+                    )
+                )
         if validators:
             if len(validators) == 1:
                 comment.append(f'{self.formater.bold("Validator")}: ' + validators[0])
             else:
-                comment.append(f'{self.formater.bold("Validators")}:' + self.formater.list(validators))
+                comment.append(
+                    f'{self.formater.bold("Validators")}:'
+                    + self.formater.list(validators)
+                )
 
-    def manage_exemples(self,
-                        multi,
-                        variable,
-                        examples_all,
-                        examples_mini,
-                        comment,
-                        ):
+    def manage_exemples(
+        self,
+        multi,
+        variable,
+        examples_all,
+        examples_mini,
+        comment,
+    ):
         example_mini = None
         example_all = None
-        example = variable.information.get("test", None)
+        example = variable.information.get("examples", None)
+        if example is None:
+            example = variable.information.get("test", None)
         default = variable.value.get()
         if isinstance(example, tuple):
             example = list(example)
-        mandatory = 'mandatory' in variable.property.get(uncalculated=True)
+        mandatory = "mandatory" in variable.property.get(uncalculated=True)
         if example:
             if not multi:
                 example = example[0]
@@ -552,9 +655,11 @@ class RougailOutputDoc:
         elif default not in [None, []]:
             example_all = default
         else:
-            example = CONVERT_OPTION.get(variable.information.get("type"), {}).get('example', None)
+            example = CONVERT_OPTION.get(variable.information.get("type"), {}).get(
+                "example", None
+            )
             if example is None:
-                example = 'xxx'
+                example = "xxx"
             if multi:
                 example = [example]
             if mandatory:
@@ -578,3 +683,7 @@ class RougailOutputDoc:
             if example_mini is not None:
                 examples_mini[variable.name()] = example_mini
             examples_all[variable.name()] = example_all
+
+
+RougailOutput = RougailOutputDoc
+__all__ = ("RougailOutputDoc",)
diff --git a/src/rougail/output_doc/annotator.py b/src/rougail/output_doc/annotator.py
index c70468a..9b4afb4 100644
--- a/src/rougail/output_doc/annotator.py
+++ b/src/rougail/output_doc/annotator.py
@@ -19,14 +19,24 @@ 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 tiramisu import undefined
 from rougail.annotator.variable import Walk
 
 from rougail.i18n import _
 from rougail.error import DictConsistencyError
-from rougail.object_model import Calculation, JinjaCalculation, VariableCalculation, \
-        InformationCalculation, IndexCalculation, SuffixCalculation, CONVERT_OPTION, \
-        PROPERTY_ATTRIBUTE
+from rougail.object_model import (
+    Calculation,
+    JinjaCalculation,
+    VariableCalculation,
+    VariablePropertyCalculation,
+    IdentifierCalculation,
+    IdentifierPropertyCalculation,
+    InformationCalculation,
+    IndexCalculation,
+    CONVERT_OPTION,
+    PROPERTY_ATTRIBUTE,
+)
 
 
 class Annotator(Walk):
@@ -45,21 +55,33 @@ class Annotator(Walk):
         self.populate_family()
         self.populate_variable()
 
-    def add_default_value(self,
-                          family,
-                          value,
-                          *,
-                          inside_list=False,
-                          ) -> None:
+    def get_examples_values(self, variable):
+        values = self.objectspace.informations.get(variable.path).get("examples", None)
+        if not values:
+            values = self.objectspace.informations.get(variable.path).get("test", None)
+        return values
+
+    def add_default_value(
+        self,
+        family,
+        value,
+        *,
+        inside_list=False,
+    ) -> None:
         if isinstance(value, Calculation):
-            default_values ='example'
+            default_values = "example"
             if not inside_list:
                 default_values = [default_values]
-            if isinstance(value, VariableCalculation):
-                variable, suffix = self.objectspace.paths.get_with_dynamic(
-                    value.variable, value.path_prefix, family.path, value.version, value.namespace, value.xmlfiles
+            if isinstance(value, (VariableCalculation, VariablePropertyCalculation)):
+                variable, identifier = self.objectspace.paths.get_with_dynamic(
+                    value.variable,
+                    value.path_prefix,
+                    family.path,
+                    value.version,
+                    value.namespace,
+                    value.xmlfiles,
                 )
-                values = self.objectspace.informations.get(variable.path).get('test', None)
+                values = self.get_examples_values(variable)
                 if values:
                     if inside_list:
                         default_values = list(values)
@@ -81,11 +103,12 @@ class Annotator(Walk):
             else:
                 for value in family.dynamic:
                     self.add_default_value(family, value, inside_list=True)
-            self.calculation_to_information(family.path,
-                                            'dynamic',
-                                            family.dynamic,
-                                            family.version,
-                                            )
+            self.calculation_to_information(
+                family.path,
+                "dynamic",
+                family.dynamic,
+                family.version,
+            )
 
     def populate_variable(self) -> None:
         """convert variables"""
@@ -93,29 +116,31 @@ class Annotator(Walk):
             if variable.type == "symlink":
                 continue
             if variable.type == "choice":
-                self.calculation_to_information(variable.path,
-                                                'choice',
-                                                variable.choices,
-                                                variable.version,
-                                                )
-            self.calculation_to_information(variable.path,
-                                            'default',
-                                            variable.default,
-                                            variable.version,
-                                            )
-            self.calculation_to_information(variable.path,
-                                            'validators',
-                                            variable.validators,
-                                            variable.version,
-                                            )
-            if variable.path in self.objectspace.leaders and \
-                    not variable.default:
-                values = self.objectspace.informations.get(variable.path).get('test', None)
+                self.calculation_to_information(
+                    variable.path,
+                    "choice",
+                    variable.choices,
+                    variable.version,
+                )
+            self.calculation_to_information(
+                variable.path,
+                "default",
+                variable.default,
+                variable.version,
+            )
+            self.calculation_to_information(
+                variable.path,
+                "validators",
+                variable.validators,
+                variable.version,
+            )
+            if variable.path in self.objectspace.leaders and not variable.default:
+                values = self.get_examples_values(variable)
                 if values:
                     variable.default = list(values)
                 else:
-                    variable.default = [CONVERT_OPTION[variable.type]['example']]
-                self.objectspace.informations.add(variable.path, 'fake_default', True)
+                    variable.default = [CONVERT_OPTION[variable.type]["example"]]
+                self.objectspace.informations.add(variable.path, "fake_default", True)
             self.objectspace.informations.add(
                 variable.path, "dictionaries", variable.xmlfiles
             )
@@ -126,58 +151,64 @@ class Annotator(Walk):
         variable: dict,
     ) -> None:
         """convert properties"""
-        for prop in ['hidden', 'disabled', 'mandatory']:
+        for prop in ["hidden", "disabled", "mandatory"]:
             prop_value = getattr(variable, prop, None)
             if not prop_value:
                 continue
-            self.calculation_to_information(variable.path,
-                                            prop,
-                                            prop_value,
-                                            variable.version,
-                                            )
+            self.calculation_to_information(
+                variable.path,
+                prop,
+                prop_value,
+                variable.version,
+            )
 
-    def calculation_to_information(self,
-                                   path: str,
-                                   prop: str,
-                                   values,
-                                   version: str,
-                                   ):
-        self._calculation_to_information(path,
-                                         prop,
-                                         values,
-                                         version,
-                                         )
+    def calculation_to_information(
+        self,
+        path: str,
+        prop: str,
+        values,
+        version: str,
+    ):
+        self._calculation_to_information(
+            path,
+            prop,
+            values,
+            version,
+        )
         if isinstance(values, list):
             for idx, val in enumerate(values):
-                self._calculation_to_information(path,
-                                                 prop,
-                                                 val,
-                                                 version,
-                                                 suffix=f'_{idx}',
-                                                 )
+                self._calculation_to_information(
+                    path,
+                    prop,
+                    val,
+                    version,
+                    identifier=f"_{idx}",
+                )
 
-    def _calculation_to_information(self,
-                                    path: str,
-                                    prop: str,
-                                    values,
-                                    version: str,
-                                    *,
-                                    suffix: str='',
-                                    ):
-        if not  isinstance(values, Calculation):
+    def _calculation_to_information(
+        self,
+        path: str,
+        prop: str,
+        values,
+        version: str,
+        *,
+        identifier: str = "",
+    ):
+        if not isinstance(values, Calculation):
             return
         values_calculation = True
         if isinstance(values, JinjaCalculation):
             if values.description:
                 values_calculation = values.description
-            values_calculation_type = 'jinja'
-        elif isinstance(values, VariableCalculation):
+            values_calculation_type = "jinja"
+        elif isinstance(values, (VariableCalculation, VariablePropertyCalculation)):
             values_calculation = values.variable
             paths = self.objectspace.paths
-            if version != '1.0' and paths.regexp_relative.search(values_calculation):
-                calculation_path = paths.get_relative_path(values_calculation,
-                                                           path,
-                                                           )
+            if version != "1.0" and paths.regexp_relative.search(values_calculation):
+                calculation_path = paths.get_full_path(
+                    values_calculation,
+                    path,
+                )
                 if prop in PROPERTY_ATTRIBUTE:
                     if values.when is not undefined:
                         values_calculation = f'when the variable "{calculation_path}" has the value "{values.when}"'
@@ -187,18 +218,27 @@ class Annotator(Walk):
                         values_calculation = f'when the variable "{calculation_path}" has the value "True"'
                 else:
                     values_calculation = calculation_path
-            values_calculation_type = 'variable'
+            values_calculation_type = "variable"
         elif isinstance(values, InformationCalculation):
-            values_calculation_type = 'information'
-        elif isinstance(values, SuffixCalculation):
-            values_calculation_type = 'suffix'
+            values_calculation_type = "information"
+        elif isinstance(values, (IdentifierCalculation, IdentifierPropertyCalculation)):
+            if version != "1.0" and prop in PROPERTY_ATTRIBUTE:
+                if values.when is not undefined:
+                    values_calculation = f'when the identifier is "{values.when}"'
+                elif values.when_not is not undefined:
+                    values_calculation = (
+                        f'when the identifier is not "{values.when_not}"'
+                    )
+            values_calculation_type = "identifier"
         elif isinstance(values, IndexCalculation):
-            values_calculation_type = 'index'
-        self.objectspace.informations.add(path,
-                                          f'{prop}_calculation_type{suffix}',
-                                          values_calculation_type,
-                                          )
-        self.objectspace.informations.add(path,
-                                          f'{prop}_calculation{suffix}',
-                                          values_calculation,
-                                          )
+            values_calculation_type = "index"
+        self.objectspace.informations.add(
+            path,
+            f"{prop}_calculation_type{identifier}",
+            values_calculation_type,
+        )
+        self.objectspace.informations.add(
+            path,
+            f"{prop}_calculation{identifier}",
+            values_calculation,
+        )
diff --git a/src/rougail/output_doc/cli.py b/src/rougail/output_doc/cli.py
deleted file mode 100644
index 9a52072..0000000
--- a/src/rougail/output_doc/cli.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""
-Cli code for Rougail-output-doc
-
-Silique (https://www.silique.fr)
-Copyright (C) 2024
-
-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 . import RougailOutputDoc
-
-
-def run(rougailconfig,
-        config,
-        user_data,
-        ):
-    inventory = RougailOutputDoc(config=config,
-                                 rougailconfig=rougailconfig,
-                                 )
-    print(inventory.gen_doc())
-
-
-__all__ = ('run',)
diff --git a/src/rougail/output_doc/config.py b/src/rougail/output_doc/config.py
index 93d659c..0491c54 100644
--- a/src/rougail/output_doc/config.py
+++ b/src/rougail/output_doc/config.py
@@ -4,43 +4,42 @@ Config file for Rougail-doc
 Silique (https://www.silique.fr)
 Copyright (C) 2024
 
-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 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 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 Lesser General Public License for more
+details.
 
-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
+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 pathlib import Path
 from rougail.utils import load_modules
-# from .utils import _
 
 
 OUTPUTS = None
 
 
 def get_outputs() -> None:
-    module_name = 'rougail.doc.output'
+    module_name = "rougail.doc.output"
     outputs = {}
-    for path in (Path(__file__).parent / 'output').iterdir():
+    for path in (Path(__file__).parent / "output").iterdir():
         name = path.name
         if not name.endswith(".py") or name.endswith("__.py"):
             continue
-        module = load_modules(module_name + '.' + name, str(path))
+        module = load_modules(module_name + "." + name, str(path))
         if "Formater" not in dir(module):
             continue
         level = module.Formater.level
         if level in outputs:
-            raise Exception(f'duplicated level rougail-doc for output "{level}": {module.Formater.name} and {outputs[level].name}')
+            raise Exception(
+                f'duplicated level rougail-doc for output "{level}": {module.Formater.name} and {outputs[level].name}'
+            )
         outputs[module.Formater.level] = module.Formater
     return {outputs[level].name: outputs[level] for level in sorted(outputs)}
 
@@ -59,9 +58,10 @@ class OutPuts:  # pylint: disable=R0903
         return OUTPUTS
 
 
-def get_rougail_config(*,
-                       backward_compatibility=True,
-                       ) -> dict:
+def get_rougail_config(
+    *,
+    backward_compatibility=True,
+) -> dict:
     outputs = list(OutPuts().get())
     output_format_default = outputs[0]
     rougail_options = """
@@ -77,20 +77,28 @@ doc:
     description: Start title level
     alternative_name: dt
     default: 1
+  with_example:
+    description: Display example in documentation
+    negative_description: Hide example in documentation
+    alternative_name: de
+    default: false
   output_format:
     description: Generate document in format
     alternative_name: do
     default: output_format_default
     choices:
-""".replace('output_format_default', output_format_default)
+""".replace(
+        "output_format_default", output_format_default
+    )
     for output in outputs:
         rougail_options += f"      - {output}\n"
-    return {'name': 'doc',
-            'process': 'output',
-            'options': rougail_options,
-            'allow_user_data': False,
-            'level': 50,
-            }
+    return {
+        "name": "doc",
+        "process": "output",
+        "options": rougail_options,
+        "allow_user_data": False,
+        "level": 50,
+    }
 
 
-__all__ = ("OutPuts", 'get_rougail_config')
+__all__ = ("OutPuts", "get_rougail_config")
diff --git a/src/rougail/output_doc/output/__init__.py b/src/rougail/output_doc/output/__init__.py
index e4581b1..490a0a4 100644
--- a/src/rougail/output_doc/output/__init__.py
+++ b/src/rougail/output_doc/output/__init__.py
@@ -2,24 +2,16 @@
 Silique (https://www.silique.fr)
 Copyright (C) 2024
 
-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 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 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 Lesser General Public License for more
+details.
 
-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
+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 echo(msg):
-    return msg
-_ = echo
diff --git a/src/rougail/output_doc/output/asciidoc.py b/src/rougail/output_doc/output/asciidoc.py
index 0bdbbea..bd7b9c9 100644
--- a/src/rougail/output_doc/output/asciidoc.py
+++ b/src/rougail/output_doc/output/asciidoc.py
@@ -1,3 +1,21 @@
+"""
+Silique (https://www.silique.fr)
+Copyright (C) 2024
+
+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 io import BytesIO
 from typing import List
 from itertools import chain
@@ -5,7 +23,7 @@ from ruamel.yaml import YAML
 
 
 class Formater:
-    name = 'asciidoc'
+    name = "asciidoc"
     level = 40
 
     def __init__(self):
@@ -13,12 +31,13 @@ class Formater:
         self._yaml.indent(mapping=2, sequence=4, offset=2)
 
     def header(self):
-        return ''
+        return ""
 
-    def title(self,
-              title: str,
-              level: int,
-              ) -> str:
+    def title(
+        self,
+        title: str,
+        level: int,
+    ) -> str:
         char = "="
         return f"{char * (level + 1)} {title}\n\n"
 
@@ -30,79 +49,91 @@ class Formater:
         stable = table.split("\n", 1)
         return stable[0].replace("<", "a") + "\n" + stable[1]
 
-    def link(self,
-             comment: str,
-             link: str,
-             ) -> str:
+    def link(
+        self,
+        comment: str,
+        link: str,
+    ) -> str:
         return f"`{link}[{comment}]`"
 
-    def prop(self,
-             prop: str,
-             ) -> str:
-        return f'`{prop}`'
+    def prop(
+        self,
+        prop: str,
+    ) -> str:
+        return f"`{prop}`"
 
-    def list(self,
-             choices: list,
-             ) -> str:
+    def list(
+        self,
+        choices: list,
+    ) -> str:
         prefix = "\n\n* "
         char = "\n* "
         return prefix + char.join([self.dump(choice) for choice in choices])
 
-    def is_list(self,
-                txt: str,
-                ) -> str:
-        return txt.startswith('* ')
+    def is_list(
+        self,
+        txt: str,
+    ) -> str:
+        return txt.startswith("* ")
 
-    def columns(self,
-                col1: List[str],
-                col2: List[str],
-                ) -> None:
+    def columns(
+        self,
+        col1: List[str],
+        col2: List[str],
+    ) -> None:
         self.max_line = 0
         for params in chain(col1, col2):
-            for param in params.split('\n'):
+            for param in params.split("\n"):
                 self.max_line = max(self.max_line, len(param))
         self.max_line += 1
 
-    def join(self,
-             lst: List[str],
-             ) -> str:
+    def join(
+        self,
+        lst: List[str],
+    ) -> str:
         string = ""
-        previous = ''
+        previous = ""
         for line in lst:
             if string:
-                if self.is_list(previous.split('\n')[-1]):
+                if self.is_list(previous.split("\n")[-1]):
                     string += "\n\n"
                 else:
                     string += " +\n"
             string += line
-                
+
             previous = line
         return "\n" + string
 
-    def to_string(self,
-                  text: str,
-                  ) -> str:
+    def to_string(
+        self,
+        text: str,
+    ) -> str:
         return text
 
-    def table_header(self,
-                     lst,
-                     ):
-        return lst[0] + " " * (self.max_line - len(lst[0])), lst[1] + " " * (self.max_line - len(lst[1]))
+    def table_header(
+        self,
+        lst,
+    ):
+        return lst[0] + " " * (self.max_line - len(lst[0])), lst[1] + " " * (
+            self.max_line - len(lst[1])
+        )
 
-    def bold(self,
-             msg: str,
-             ) -> str:
+    def bold(
+        self,
+        msg: str,
+    ) -> str:
         return f"**{msg}**"
 
-    def italic(self,
-               msg: str,
-               ) -> str:
+    def italic(
+        self,
+        msg: str,
+    ) -> str:
         return f"_{msg}_"
 
     def dump(self, dico):
         with BytesIO() as ymlfh:
             self._yaml.dump(dico, ymlfh)
-            ret = ymlfh.getvalue().decode('utf-8').strip()
-        if ret.endswith('...'):
+            ret = ymlfh.getvalue().decode("utf-8").strip()
+        if ret.endswith("..."):
             ret = ret[:-3].strip()
         return ret
diff --git a/src/rougail/output_doc/output/github.py b/src/rougail/output_doc/output/github.py
index ef5ca0b..47fe0ab 100644
--- a/src/rougail/output_doc/output/github.py
+++ b/src/rougail/output_doc/output/github.py
@@ -1,3 +1,21 @@
+"""
+Silique (https://www.silique.fr)
+Copyright (C) 2024
+            
+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 io import BytesIO
 from typing import List
 from itertools import chain
@@ -5,7 +23,7 @@ from ruamel.yaml import YAML
 
 
 class Formater:
-    name = 'github'
+    name = "github"
     level = 50
 
     def __init__(self):
@@ -15,14 +33,15 @@ class Formater:
 
     def header(self):
         if self.header_setted:
-            return ''
+            return ""
         self.header_setted = True
         return "---\ngitea: none\ninclude_toc: true\n---\n"
 
-    def title(self,
-              title: str,
-              level: int,
-              ) -> str:
+    def title(
+        self,
+        title: str,
+        level: int,
+    ) -> str:
         char = "#"
         return f"{char * level} {title}\n\n"
 
@@ -32,67 +51,77 @@ class Formater:
     def table(self, table):
         return table
 
-    def link(self,
-             comment: str,
-             link: str,
-             ) -> str:
+    def link(
+        self,
+        comment: str,
+        link: str,
+    ) -> str:
         return f"[`{comment}`]({link})"
 
-    def prop(self,
-             prop: str,
-             ) -> str:
-        return f'`{prop}`'
+    def prop(
+        self,
+        prop: str,
+    ) -> str:
+        return f"`{prop}`"
 
-    def list(self,
-             choices,
-             ):
+    def list(
+        self,
+        choices,
+    ):
         prefix = "<br/>- "
         char = "<br/>- "
         return prefix + char.join([self.dump(choice) for choice in choices])
 
-    def is_list(self,
-                txt: str,
-                ) -> str:
-        return txt.startswith('* ')
-    
-    def columns(self,
-                col1: List[str],
-                col2: List[str],
-                ) -> None:
+    def is_list(
+        self,
+        txt: str,
+    ) -> str:
+        return txt.startswith("* ")
+
+    def columns(
+        self,
+        col1: List[str],
+        col2: List[str],
+    ) -> None:
         self.max_line = 0
         for params in chain(col1, col2):
-            for param in params.split('\n'):
+            for param in params.split("\n"):
                 self.max_line = max(self.max_line, len(param))
         self.max_line += 1
 
-    def join(self,
-             lst: List[str],
-             ) -> str:
+    def join(
+        self,
+        lst: List[str],
+    ) -> str:
         return "<br/>".join(lst)
 
-    def to_string(self,
-                  text: str,
-                  ) -> str:
-        return text.strip().replace('\n', '<br/>')
+    def to_string(
+        self,
+        text: str,
+    ) -> str:
+        return text.strip().replace("\n", "<br/>")
 
-    def table_header(self,
-                     lst):
-        return lst[0] + "&nbsp;" * (self.max_line - len(lst[0])), lst[1] + "&nbsp;" * (self.max_line - len(lst[1]))
+    def table_header(self, lst):
+        return lst[0] + "&nbsp;" * (self.max_line - len(lst[0])), lst[1] + "&nbsp;" * (
+            self.max_line - len(lst[1])
+        )
 
-    def bold(self,
-             msg: str,
-             ) -> str:
+    def bold(
+        self,
+        msg: str,
+    ) -> str:
         return f"**{msg}**"
 
-    def italic(self,
-               msg: str,
-               ) -> str:
+    def italic(
+        self,
+        msg: str,
+    ) -> str:
         return f"*{msg}*"
 
     def dump(self, dico):
         with BytesIO() as ymlfh:
             self._yaml.dump(dico, ymlfh)
-            ret = ymlfh.getvalue().decode('utf-8').strip()
-        if ret.endswith('...'):
+            ret = ymlfh.getvalue().decode("utf-8").strip()
+        if ret.endswith("..."):
             ret = ret[:-3].strip()
         return ret