feat: documentation

This commit is contained in:
egarette@silique.fr 2023-12-17 20:25:53 +01:00
parent d8d3f89fd6
commit 4660a65303
35 changed files with 3153 additions and 2106 deletions

32
.readthedocs.yaml Normal file
View file

@ -0,0 +1,32 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"
# You can also specify other tool versions:
# nodejs: "19"
# rust: "1.64"
# golang: "1.19"
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub
# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: docs/requirements.txt

View file

@ -95,7 +95,7 @@ The destination file is generated with new values:
# Link # Link
* [Documentation](https://silique.fr/rougail/) * [Documentation](https://rougail.readthedocs.io/en/latest/)
* [Licence ](LICENSE) * [Licence ](LICENSE)
# Related projects # Related projects

View file

@ -1,28 +0,0 @@
![Logo Rougail](../logo.png "logo rougail")
# Rougail
Rougail est une bibliothèque python3 qui permet de charger des dictionnaires (fichiers au format YAML) dans le but de charger les variables dans Tiramisu.
![Schéma](schema.png "Schéma")
[Débutons avec Rougail](getting_started.md).
## La bibliothèque
- [La bibliothèque](dev/README.md)
- [La configration de la bibliothèque](dev/config.md)
## Les dictionnaires
- [Les dictionnaires](dictionary/rougail.md)
- [Convention d'écriture d'un dictionnaire](dictionary/convention.md)
### Les variables
- [Les variables](variable/README.md)
- [Les familles](family/README.md)
- [Les variables à valeur par défaut calculées](fill/README.md)
- [Les vérifications des valeurs](check/README.md)
- [Les propriétés calculées](condition/README.md)

View file

@ -1,210 +0,0 @@
# Fonction de vérification
## Synopsis
Une fonction de vérification est une fonction complémentaire au type qui permet de valider plus précisement le contenu d'une variable.
Un validateur est forcement un calcul de type Jinja.
## Paramètres
Suivant les types de calcul les paramètres vont être différents :
| Type de calcul | Paramètre | Commentaires | Exemple |
|----------------|-----------|--------------|---------|
| | **type**<br/>`string`<br/>`mandatory` | Type du calcul, la seule valeur possible est : jinja | jinja |
| **jinja**<br/>`string`<br/>`mandatory` | Template Jinja. | {% if rougail.variable == 'not\_allowed' %}not allowed!{% endif %} |
| **params**<br/>`list` | Paramètres complémentaire passé au template Jinja | |
Il existe deux types de paramètre :
- les paramètres standards (string, boolean, integer, null), dans ce il suffit de faire : "key: value"
- les paramètres avancés :
- paramètre via une variable
- paramètre via une information
- paramètre via un suffix : dans le cas d'une variable dans une famille dynamique
- paramètre via un index : dans le cas d'une variable suiveuse
| Type du paramètre | Paramètre | Commentaires | Exemple |
|-------------------|-----------|--------------|---------|
| | **name**<br/>`string`<br/>`mandatory` | Le nom du paramètre | my\_param |
| | **type**<br/>`string`<br/>`mandatory` | Type du paramètre, les valeurs possible sont : variable, information, suffix ou index| suffix |
| Variable | **variable**<br/>`string`<br/>`mandatory` | Nom de la variable | rougail.variable |
| Variable (`mandatory`)<br/>Information | **propertyerror**<br/>`boolean` | Si l'accès à la variable n'est pas possible à cause d'une propriété (par exemple `disabled`) par défaut une erreur est retournée. Si l'attribut est à False, le paramètre n'est pas passé au template Jinja.<br/>**Valeur par défaut :** True | False |
| Variable | **optional**<br/>`boolean` | La variable peut ne pas exister suivant les importations de fichier YAML. Si le paramètre optional est à True, le paramètre sera tout simplement supprimer si la variable n'existe pas.<br/>**Valeur par défaut :** False | True |
| Information | **information**<br/>`string`<br/>`mandatory` | Nom de l'information dont on veut récupérer la valeur. | doc |
## Exemples
### Vérification stricte des valeurs
Voici un exemple simple de validation des valeurs :
```yml
---
version: '1.0'
my_variable:
validators:
- type: jinja
jinja: |
{% if rougail.my_variable and not rougail.my_variable.islower() %}
{{ rougail.my_variable }} is not lowercase string
{% endif %}
```
Une fonction de vérification doit prendre en compte 2 aspects important :
- la valeur peut ne pas être renseigné (même si la variable est obligatoire), la valeur None doit être prise en compte
- si la valeur est invalide, il faut renvoyer une phrase avec un message explicite.
À partir de maintenant seule None et des valeurs en minuscule seront autorisés.
### Vérification des valeurs avec avertissement
Dans la contrainte, il est possible de spécifier le niveau d'erreur et le mettre en avertissement :
```yml
---
version: '1.0'
my_variable:
validators:
- type: jinja
jinja: |+
{% if rougail.my_variable and not rougail.my_variable.islower() %}
{{ rougail.my_variable }} is not lowercase string
{% endif %}
params:
warnings_only: true
```
Dans ce cas une valeur avec une majuscule sera accepté, mais un message d'avertissement apparaitra.
### Vérification avec paramètres :
```yml
---
version: '1.0'
my_hidden_variable:
disabled: true
my_variable:
validators:
- type: jinja
jinja: |
{% if param1 is defined and rougail.my_variable == param1 %}
has same value as rougail.unknown_variable
{% endif %}
{% if param2 is defined and rougail.my_variable == param2 %}
has same value as rougail.my_hidden_variable
{% endif %}
params:
param1:
type: variable
variable: rougail.unknown_variable
optional: true
param2:
type: variable
variable: rougail.my_hidden_variable
propertyerror: false
```
Un exemple avec un paramètre de type suffix :
```yml
---
version: '1.0'
varname:
multi: true
default:
- val1
- val2
my_dyn_family_:
type: dynamic
variable: rougail.varname
description: 'Describe '
my_dyn_var:
type: string
validators:
- type: jinja
jinja: |
{% if rougail.my_dyn_family_.my_dyn_var == param1 %}
forbidden!
{% endif %}
params:
param1:
type: suffix
```
Dans cette exemple, on voit une famille dynamique. Deux familles vont être crées : `rougail.my_dyn_family_val1.my_dyn_var` et `rougail.my_dyn_family_val2.my_dyn_var`.
La valeur de la variable à l'intérieur de cette famille ne peux pas être égale à la valeur du suffix (respectivement `val1` et `val2`).
Un exemple avec un paramètre de type index :
```yml
---
version: '1.0'
family:
type: leadership
leader:
multi: true
default:
- val1
- val2
follower1:
type: number
validators:
- type: jinja
jinja: |
{% if rougail.family.follower1 == param1 %}
forbidden!
{% endif %}
params:
param1:
type: index
```
### Rédéfinition
Dans un premier dictionnaire déclarons notre variable et sa fonction de vérification :
```yml
---
version: '1.0'
my_variable:
validators:
- type: jinja
jinja: |
{% if rougail.my_variable and not rougail.my_variable.islower() %}
{{ rougail.my_variable }} is not lowercase string
{% endif %}
```
Dans un second dictionnaire il est possible de redéfinir le calcul :
```yml
---
version: '1.0'
my_variable:
redefine: true
validators:
- type: jinja
jinja: |
{% if rougail.my_variable and ' ' in rougail.my_variable %}
{{ rougail.my_variable }} has a space
{% endif %}
```
Dans ce cas seule ce validateur sera exécuté.
Voici un troisième dictionnaire dans lequel on supprime la validation :
```yml
---
version: '1.0'
my_variable:
redefine: true
validators:
```

View file

@ -1,125 +0,0 @@
---
gitea: none
include_toc: true
---
# Les propriétés calculées
## Synopsis
Les propriétés calculées permettent d'ajouter ou de supprimer des propriétés à une [variable](../variable/README.md) ou une [famille](../family/README.md) suivant le contexte.
Voici la liste des propriétés modifiables :
| Attribut applicable sur | Nom de la propriétée | Commentaire |
|-------------------------|----------------------|-------------|
| Variable<br/>Famille | hidden | Cache une variable ou famille, dans ce cas elle n'est pas accessible en lecture écriture, mais reste accessible dans un calcul ou en lecture seule |
| Variable<br/>Famille | disabled | Désactive une variable ou famille, dans ce cas elle n'est jamais accessible |
| Variable | mandatory | La variable attend une valeur autre que None ou [] pour les variables multiple |
Une propriété peut être calculée. Dans ce cas on a deux possibilités :
- calcul via Jinja
- calcul via une variable
## Paramètres
Suivant les types de calcul les paramètres vont être différents :
| Type de calcul | Paramètre | Commentaires | Exemple |
|----------------|-----------|--------------|---------|
| | **type**<br/>`string`<br/>`mandatory` | Type du calcul, les valeurs possible sont : jinja, variable, information, suffix ou index | jinja |
| Jinja | **jinja**<br/>`string`<br/>`mandatory` | Template Jinja. Pour une variable multiple, chaque ligne représente une valeur. | {% if rougail.variable %}{{ rougail.variable }}{% endif %} |
| Jinja | **params**<br/>`list` | Paramètres complémentaire passé au template Jinja | |
| Variable | **variable**<br/>`string`<br/>`mandatory` | Nom de la variable associée. ⚠️ La variable doit être de type `boolean`. | rougail.variable |
| Variable | **propertyerror**<br/>`boolean` | Si l'accès à la variable n'est pas possible à cause d'une propriété (par exemple `disabled`) par défaut une erreur est retournée. Si l'attribut est à False, la valeur calculée est False.<br/>**Valeur par défaut :** True | False |
Dans le cas d'un calcul de type Jinja, il est possible d'avoir des paramètres.
Il existe deux types de paramètre :
- les paramètres standards (string, boolean, integer, null), dans ce il suffit de faire : "key: value"
- les paramètres avancés :
- paramètre via une variable
- paramètre via une information
- paramètre via un suffix : dans le cas d'une variable dans une famille dynamique
- paramètre via un index : dans le cas d'une variable suiveuse
| Type du paramètre | Paramètre | Commentaires | Exemple |
|-------------------|-----------|--------------|---------|
| | **name**<br/>`string`<br/>`mandatory` | Le nom du paramètre | my\_param |
| | **type**<br/>`string`<br/>`mandatory` | Type du paramètre, les valeurs possible sont : variable, information, suffix ou index | suffix |
| Variable | **variable**<br/>`string`<br/>`mandatory` | Nom de la variable | rougail.variable |
| Variable (`mandatory`)<br/>Information | **propertyerror**<br/>`boolean` | Si l'accès à la variable n'est pas possible à cause d'une propriété (par exemple `disabled`) par défaut une erreur est retournée. Si l'attribut est à False, le paramètre n'est pas passé au template Jinja.<br/>**Valeur par défaut :** True | False |
| Variable | **optional**<br/>`boolean` | La variable peut ne pas exister suivant les importations de fichier YAML. Si le paramètre optional est à True, le paramètre sera tout simplement supprimer si la variable n'existe pas.<br/>**Valeur par défaut :** False | True |
| Information | **information**<br/>`string`<br/>`mandatory` | Nom de l'information dont on veut récupérer la valeur. | doc |
## Exemples
### Une propriété calculée de type Jinja
Il est possible d'écrire la condition en Jinja :
```yml
---
version: '1.0'
condition:
default: 'do not hide!'
my_variable:
hidden:
type: jinja
jinja: |
{% if rougail.condition and rougail.condition == "hide!" %}
this rougail.condition value is 'hide!'
{% endif %}
```
Dans ce cas la variable est cachée si la valeur de la variable "rougail.condition" est `hide!` et elle n'a pas caché pour tout autre valeur.
Attention toujours prendre en considération que "rougail.condition" peut être égale à `None`.
Le message retourner par la fonction est visible dans le message d'erreur en cas de problème d'accès :
```python
>>> from rougail import Rougail, RougailConfig
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.property.read_write()
[..]
tiramisu.error.PropertiesOptionError: cannot access to option "my_variable" because has property "hidden" (this rougail.condition value is 'hide!')
```
Il est possible d'utiliser des paramètres lors du calcul des propriétés comme pour le calcul de l'attribut [`default`](../fill/README.md).
### Une propriété calculée de type variable
Une variable peut donc être calculé via la résultat d'une autre variable.
Attention, cette autre variable doit obligatoirement être de type `boolean` :
```yml
---
version: '1.0'
condition:
type: boolean
my_variable:
hidden:
type: variable
variable: rougail.condition
```
Si la valeur de la variable "rougail.condition" est `True` alors la variable "rougail.my\_variable" est cachée.
### Rédéfinition
Il se peut que dans un dictionnaire on décide de définir une condition.
Pour supprimer le calcul à partir d'une variable il suffit de faire dans un nouveau dictionnaire :
```yml
---
version: '1.0'
my_variable:
redefine: true
hidden:
```

View file

@ -1,131 +0,0 @@
# La bibliothèque Rougail
Rougail est une bibliothèque de gestion de configuration qui permet de charger simplement des variables.
Dans les exemples suivants, nous utiliserons une configuration particulière de Rougail.
Vous retrouverez toutes les options pour [personnaliser les répertoires utilisés](config.md).
Pour charger la configuration il faut importer la variable RougailConfig et changer les valeurs :
```python
from rougail import RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
```
## Convertisons un dictionnaire
Un dictionnaire est un ensemble d'instruction qui vont permettre de créer des familles et des variables.
Commençons par créer un [dictionnaire](../dictionary/rougail.md) simple.
Voici un premier dictionnaire dict/00-base.yml :
```yml
---
version: '1.0'
my_variable:
default: my_value
```
Puis, créons les objets [Tiramisu](https://forge.cloud.silique.fr/gnunux/tiramisu) via la script script.py suivant :
```python
from rougail import Rougail, RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
rougail = Rougail()
config = rougail.get_config()
print(config.value.get())
```
Exécution le script :
```sh
$ python3 script.py
{'rougail.my_variable': 'my_value'}
```
## Convertisons un dictionnaire extra
L'espace de nommage par défaut des variables et familles est "rougail". Il est possible de définir d'autres espaces de nom.
Ces espaces de nom additionnels s'appelle des "[extras](../dictionary/extra.md)".
Les espaces de nom additionnels se définissent lors de la configuration.
Par exemple, voici comment ajouter une espace de nom "example" :
```python
RougailConfig['extra_dictionaries']['example'] = ['extras/']
```
Ensuite créons un dictionnaire extra extras/00-base.yml :
```yml
---
version: '1.0'
my_variable_extra:
default: my_value_extra
```
Puis, créons les objets [Tiramisu](https://forge.cloud.silique.fr/gnunux/tiramisu) via la script script.py suivant :
```python
from rougail import Rougail, RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['extra_dictionaries']['example'] = ['extras/']
rougail = Rougail()
config = rougail.get_config()
print(config.value.dict())
```
Exécution le script :
```python
$ python3 script.py
{'rougail.my_variable': 'my_value', 'example.my_variable_extra': 'my_value_extra'}
```
## Créons une fonction personnalisé
Nous créons le dictionnaire complémentaire dict/01-function.yml pour que la variable "my_variable_jinja" soit [calculée](fill/README.md) :
```yml
---
version: '1.0'
my_variable_jinja:
type: "string"
default:
type: jinja
jinja: "{{ return_no() }}"
```
Puis créons la fonction "return_no" dans functions.py :
```python
def return_no():
return 'no'
```
Puis, créons les objets [Tiramisu](https://forge.cloud.silique.fr/gnunux/tiramisu) via la script script.py suivant :
```python
from rougail import Rougail, RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['extra_dictionaries']['example'] = ['extras/']
RougailConfig['functions_file'] = 'functions.py'
rougail = Rougail()
config = rougail.get_config()
print(config.value.dict())
```
Exécution le script :
```python
$ python3 script.py
{'rougail.my_variable': 'my_value', 'rougail.my_variable_jinja': 'no', 'example.my_variable_extra': 'my_value_extra'}
```
La valeur de la variable `my_variable_extra` est bien calculé à partir de la fonction `return_no`.

View file

@ -1,94 +0,0 @@
# Personnalisons la configuration de Rougail
La configuration de Rougail se trouve dans l'objet `RougailConfig` :
```python
from rougail import RougailConfig
```
C'est un simple dictionnaire python avec différentes clefs.
Pour modifier il suffit de faire :
```python
RougailConfig[key] = value
```
## Configuration de chargement des dictionnaires
### Les répertoires des dictionnaires
Il existe deux types de répertoires de dictionnaires :
- les dictionnaires principaux avec la clef `dictionaries_dir`. La valeur par défaut est `['/srv/rougail/dictionaries']`. Cette variable doit contenir la liste des répertoires contenants des dictionnaires.
- les dictionnaires extra avec la clef `extra_dictionaries`. La valeur est un dictionnaire avec l'ensemble des espaces de nom. La clef étant l'espace de nom et la valeur étant une liste de répertoire.
Par exemple pour ajouter l'extra `example` il faut faire :
```python
RougailConfig['extra_dictionaries']['example'] = ['/dir1', '/dir2']
```
Les dictionnaires sont chargés dans le même ordre que les dictionnaires principaux.
### Le fichier de fonction
Le fichier qui contient les fonctions personnalisés est géré dans la clef "functions_file" et a comme valeur par défaut "/srv/rougail/functions.py". Cette clef peut contenir une liste s'il y a plusieurs fichiers.
Les fonctions doivent retourner une valeur, même si la variable que l'on calcul est une variable multiple.
Si la fonction peut retourner une valeur multiple (une liste), il faut mettre le nom de la fonction dans la clef "multi_functions".
### La variable auto_freeze
La propriété auto_freeze n'est appliqué que une variable spécifique passe à True. Par défaut le nom de la variable est "instancied_module", mais il est possible de changer le nom de cette variable via la clef "auto_freeze_variable".
### Les modes
Les modes sont personnalisables dans Rougail. Par défaut les modes sont "basic", "normal" et "expert".
Il est possible de changer cette liste via la clef "modes_level".
Si vous changer ces valeurs, penser à changer les modes par défaut des familles et des variables.
### Le mode par défaut pour une famille
Le mode par défaut d'une famille est "basic". Il est possible de changer le mode par défaut d'une famille via la clef "default_family_mode".
### Le mode par défaut pour une variable
Le mode par défaut d'une variable est "normal". Il est possible de changer le mode par défaut d'une variable via la clef "default_variable_mode".
### Le nom des fonctions internes
Il est possible d'ajouter des fonctions interne via la clef "internal_functions".
## Configuration de la templatisation
### Le répertoire des templates
Le répertoire des templates est géré dans la clef "templates_dir" et a comme valeur par défaut : "/srv/rougail/templates". Cette clef peut contenir une liste s'il y a plusieurs répertoires.
### Le répertoire des patchs
Le répertoire des patches est géré dans la clef "patches_dir" et a comme valeur par défaut : "/srv/rougail/patches".
### Le répertoire temporaire
Le répertoire temporaire est utile lors de la génération de template. Il contient une copie des templates avec, éventuellement, les patches appliqués sur les templates.
Le répertoire de temporaire est géré dans la clef "tmp_dir" et a comme valeur par défaut : "/srv/rougail/tmp".
### Le répertoire de destination des fichiers générés
Le répertoire de destination des fichiers générés est géré dans la clef "destinations_dir" et a comme valeur par défaut : "/srv/rougail/destinations".
### Les informations systemd
Un certain nombre de variables concerne les templates systemd.
## Ajout d'une fonction de conversion
Les fonctions de conversion font partie du moteur de rougail. Il converti les informations des dictionnaires pour créer des variables Tiramisu.
La clef "extra_annotators" permet d'ajouter des fonctions complémentaires.

View file

@ -1,20 +0,0 @@
---
gitea: none
include_toc: true
---
# Conventions
## Convention de nom d'un fichier de dictionnaire
L'ordre des dictionnaires est important pour l'ordre de création des variables et des familles.
Les fichiers devront donc démarrés par deux numéros suivit d'un tiret.
Par exemple : `00-base.xml`
## Convention du nom des familles et variables
La seule restriction sur le nom des familles et variables est que le nom ne doit pas commencer par le caractère "\_".
Néanmoins il est préférable de n'utiliser que des lettres minuscule ASCII, des chiffres et le caractère "\_".
C'est la convention typographique snake case qui est donc utilisée.

View file

@ -1,34 +0,0 @@
---
gitea: none
include_toc: true
---
# Les dictionnaires
## Un dictionnaire ?
Un dictionnaire est un fichier YAML dont la structure est décrite dans cette documentation.
Un dictionnaire contient en ensemble de variable chargé dans [Tiramisu](https://forge.cloud.silique.fr/gnunux/tiramisu), utilisable à tout moment, notamment dans des templates.
Les familles et les variables peuvent être définis dans plusieurs dictionnaires. Ces dictionnaires s'aggrègent alors.
Les dictionnaires sont chargés dans l'ordre des répertoires [définit avec le paramètre `dictionaries_dir` de la configuration](../dev/config.md). Chaque répertoire est chargé les uns après les autres. A l'intérieur de ces répertoires les fichiers YAML seront classés par ordre alphabétique.
Il n'y a pas de classement par ordre alphabétique de l'ensemble des fichiers YAML de tous les répertoires.
Il est également possible de redéfinir des éléments pour changer les comportement d'une famille ou d'une variable.
## L'espace de nom par défaut
Les familles et variables de ces dictionnaires sont classées, par défaut, dans l'espace de nom `rougail`. Il est possible de changer le nom de cet espace de nom [avec le paramètre `variable_namespace` de la configuration](../dev/config.md).
Cet espace de nom est un peu particulier, il peut accéder a des variables dans un autre espace de nom.
## Les dictionnaires extra
Un extra est un espace de nom différent. L'idée et de pouvoir classer les variables par thématique.
Les espaces de nom extra doivent être déclaré au moment [de la configuration de Rougail](../dev/config.md).
Dans cet espace de nom on ne peut pas accéder à des variables d'un autre espace de nom `extra`. Par contre il est possible d'accéder au variable de l'espace de nom par défaut.

View file

@ -1,146 +0,0 @@
---
gitea: none
include_toc: true
---
# Une famille
## Synopsis
Une famille est un conteneur de variables et de sous-famille.
⚠️ Une famille sans sous-famille ni sous-variable sera automatiquement supprimée.
## Paramètres
| Paramètre | Commentaires |
|-----------|--------------|
| **name**<br/>`string`<br/>`mandatory` | Nom de la famille.<br/>C'est avec ce nom qu'on va pouvoir interagir avec la famille.<br/>Il est préférable de suivre la [convention sur les noms de variable](convention.md). |
| **type**, **\_type**<br/>`string` | Type de la famille.<br/>Le type n'est pas obligatoire même si parfois c'est nécessaire de le faire pour aider le moteur.<br/>**Valeurs possible :**<br/>- `family` ← par defaut<br/>- `leadership`<br/>- `dynamic`<br/>📝 Si une sous-famille ou une sous-variable a déjà le nom "type" il est possible d'utiliser l'attribut "\_type". |
| **description**, **\_description**<br/>`string` | La description de la famille.<br/>Information utilisateur permettant de comprendre l'utilité de la famille.<br/>📝 Si une sous-famille ou une sous-variable a déjà le nom "description" il est possible d'utiliser l'attribut "\_description". |
| **help**, **\_help**<br/>`string` | Aide complémentaire associée à la famille.<br/>📝 Si une sous-famille ou une sous-variable a déjà le nom "help" il est possible d'utiliser l'attribut "\_help". |
| **mode**, **\_mode**<br/>`string` | Mode de la famille<br/>Le mode par défaut d'une famille est le mode le plus petit des familles parentes, les variables enfants ou des familles enfants qui sont contenus dans cette famille.<br/> Ce mode permet aussi de définir le mode par défaut des variables ou des familes inclusent dans cette famille.<br/>📝 Si une sous-famille ou une sous-variable a déjà le nom "mode" il est possible l'attribut "\_mode". |
| **hidden**, **\_hidden**<br/>`boolean` ou [`calcul`](../condition/README.md) | Famille invisible.<br/>Permet de cacher une famille ainsi que les variables ou les familles inclusent dans cette famille.<br/>Cela signifie que la famille ne sera plus visible pour l'utilisateur mais sera visible pour un calcul.<br/>📝 Si une sous-famille ou une sous-variable a déjà le nom "hidden" il est possible l'attribut "\_hidden". |
| **disabled**, **\_disabled**<br/>`boolean` ou [`calcul`](../condition/README.md) | Famille désactivée.<br/>Permet de désactiver une famille ainsi que les variables ou les familles inclusent dans cette famille.<br/>Cela signifie que la famille ne sera plus visible pour l'utilisateur mais également pour un calcul.<br/>📝 Si une sous-famille ou une sous-variable a déjà le nom "disabled" il est possible l'attribut "\_disabled". |
## Famille crée dynamiquement
Pour créer une famille dynamiquement, il faut créer une famille fictive liée à une variable.
Le nom et la description de la famille sera en réalité le prefix du nouveau nom/description. Le suffix viendra de la valeur de la variable liée.
Le nom des familles et des variables qu'elle contient sera conservé, par contre, la description sera le prefix de la description réelle.
Bien évidement si le contenu de variable liée venait a évoluer, de nouvelles familles dynamiques apparaitront ou disparaîtront.
A noter que :
- la variable liée à la famille doit être obligatoirement une variable multiple
- il n'est pas possible de mettre une simple famille dans une famille dynamique
- il est possible de mettre une famille meneuse dans une famille dynamique
## Variable meneuse ou suiveuse
Une famille meneuse a un attribut type à "leadership". Le type est obligatoire.
### Un famille meneuse
Les variables meneuses et suiveuses sont placées dans un famille meneuse.
Une famille meneuse ne peut pas contenir d'autre famille.
Le mode par défaut de la famille meneuse est le mode de la variable meneuse.
### Variable meneuse
Une variable meneuse est une variable qui va guider la longueur d'autres variables (appelé variables suiveuses).
Une variable meneuse est une [variable](../variable/README.md) qui est obligatoirement de type multiple.
Une variable meneuse peut être obligatoire.
Le mode par défaut correspond au plus petit mode définit pour les variables suiveuses.
### Variable suiveuse
Une variable suiveuse est une variable donc la longueur n'est pas déterminé par elle-même, mais est identique à celle de la variable meneuse dont elle dépend.
Une variable suiveuse est une variable placer juste derrière une variable meneuse ou une autre variable suiveuse.
L'ordre de définition des variables suiveuses est important.
Cette variable peut être de type multiple. Dans ce cas, pour un index de la variable meneuse determiné, il est possible de mettre plusieurs valeurs à une même variable.
Une variable suiveuse peut être obligatoire. Cela signifie que lorsqu'une variable meneuse est renseigné, il faut obligatoirement que la variable suiveuse est également une valeur à l'index considéré.
Si aucune valeur n'est définit pour la variable meneuse, aucune valeur n'est a spécifié pour la variable suiveuse.
Le mode par défaut d'une variable suiveuse correspond au mode de la variable meneuse.
Si une variable meneuse est caché ou désactivé, les variables suiveuses le seront également.
## Exemple
### Famille simple
```yml
---
version: '1.0'
my_family:
type: family
description: This is a great family
help: This is the help of a great family
mode: expert
```
### Famille crée dynamiquement
```yml
---
version: '1.0'
varname:
multi: true
default:
- val1
- val2
my_dyn_family_:
type: dynamic
variable: rougail.varname
description: 'Describe '
my_dyn_var:
type: string
description: 'Variable description for '
```
Créera dynamiquement deux familles :
- "rougail.my\_dyn\_family\_val1" avec la description "Describe val1"
- "rougail.my\_dyn\_family\_val2" avec la description "Describe val2"
Dans la famille dynamique "rougail.my\_dyn\_family\_val1" on retrouvera une variable "my\_dyn\_var" avec la description "Variable description for val1".
### Variable meneuse ou suiveuse
#### Définition des variables meneuse et suiveuse
Voici un exemple de définition d'une variable meneuse et de deux variables meneuses :
```yml
---
version: '1.0'
family:
type: leadership
leader:
multi: true
follower1:
follower2:
multi: true
```
#### Ajout d'une nouvelle variable suiveuse
Pour ajouter une nouvelle variable suiveuse, dans un nouveau dictionnaire, il suffit de définir une ou des nouvelles variables dans la famille meneuse :
```yml
---
version: '1.0'
family:
follower3:
```

View file

@ -1,372 +0,0 @@
---
gitea: none
include_toc: true
---
# Les valeurs par défault calculées
## Synopsis
Une valeur peut être calculée. Dans ce cas on a quatre possibilités :
- calcul via Jinja
- calcul via une variable
- calcul via une information
- calcul via un suffix : dans le cas d'une variable dans une famille dynamique
- calcul via un index : dans le cas d'une variable suiveuse
Si l'utilisateur modifie la valeur de la variable, la valeur par défaut n'est plus utilisé, donc le calcul n'est plus réalisé.
C'est le cas également si la variable à l'attribut `auto_save`.
Par contre si la variable est caché (avec le paramètre `hidden`) c'est la valeur par défaut qui est utilisé et non la valeur personnalisée par l'utilisateur.
⚠️ Une variable suiveuse ne peut pas être calculé automatiquement.
## Paramètres
Suivant les types de calcul les paramètres vont être différents :
| Type de calcul | Paramètre | Commentaires | Exemple |
|----------------|-----------|--------------|---------|
| | **type**<br/>`string`<br/>`mandatory` | Type du calcul, les valeurs possible sont : jinja, variable, information, suffix ou index | jinja |
| Jinja | **jinja**<br/>`string`<br/>`mandatory` | Template Jinja. Pour une variable multiple, chaque ligne représente une valeur. | {% if rougail.variable %}{{ rougail.variable }}{% endif %} |
| Jinja | **params**<br/>`list` | Paramètres complémentaire passé au template Jinja | |
| Variable (`mandatory`)<br/>Information | **variable**<br/>`string` | Nom de la variable associée | rougail.variable |
| Variable | **propertyerror**<br/>`boolean` | Si l'accès à la variable n'est pas possible à cause d'une propriété (par exemple `disabled`) par défaut une erreur est retournée. Si l'attribut est à False, la valeur calculée est vide.<br/>**Valeur par défaut :** True | False |
| Information | **information**<br/>`string`<br/>`mandatory` | Nom de l'information dont on veut récupérer la valeur. | doc |
Dans le cas d'un calcul de type Jinja, il est possible d'avoir des paramètres.
Il existe deux types de paramètre :
- les paramètres standards (string, boolean, integer, null), dans ce il suffit de faire : "key: value"
- les paramètres avancés :
- paramètre via une variable
- paramètre via une information
- paramètre via un suffix : dans le cas d'une variable dans une famille dynamique
- paramètre via un index : dans le cas d'une variable suiveuse
| Type du paramètre | Paramètre | Commentaires | Exemple |
|-------------------|-----------|--------------|---------|
| | **name**<br/>`string`<br/>`mandatory` | Le nom du paramètre | my\_param |
| | **type**<br/>`string`<br/>`mandatory` | Type du paramètre, les valeurs possible sont : variable, information, suffix ou index | suffix |
| Variable | **variable**<br/>`string`<br/>`mandatory` | Nom de la variable | rougail.variable |
| Variable (`mandatory`)<br/>Information | **propertyerror**<br/>`boolean` | Si l'accès à la variable n'est pas possible à cause d'une propriété (par exemple `disabled`) par défaut une erreur est retournée. Si l'attribut est à False, le paramètre n'est pas passé au template Jinja.<br/>**Valeur par défaut :** True | False |
| Variable | **optional**<br/>`boolean` | La variable peut ne pas exister suivant les importations de fichier YAML. Si le paramètre optional est à True, le paramètre sera tout simplement supprimer si la variable n'existe pas.<br/>**Valeur par défaut :** False | True |
| Information | **information**<br/>`string`<br/>`mandatory` | Nom de l'information dont on veut récupérer la valeur. | doc |
## Exemples
### Calcul via un template Jinja
Commençons par exemple à partir d'un simple template Jinja :
```yml
---
version: '1.0'
my_calculated_variable:
default:
type: jinja
jinja: 'no'
```
Voici un deuxième exemple avec une variable de type booléen :
```yml
---
version: '1.0'
my_calculated_variable:
type: boolean
default:
type: jinja
jinja: 'false'
```
Et une valeur multiple de type nombre :
```yml
---
version: '1.0'
my_calculated_variable:
type: number
multi: true
default:
type: jinja
jinja: |
1
2
3
```
Créons une variable dont la valeur est retournée par une fonction Python :
```yml
---
version: '1.0'
my_calculated_variable:
default:
type: jinja
jinja: '{{ return_no() }}'
```
Puis créons la fonction "return\_no" :
```python
def return_no():
return 'no'
```
Un exemple avec des paramètres :
```yml
---
version: '1.0'
my_calculated_variable:
description: my description
default:
type: jinja
jinja: |
{{ param1 }}{% if param2 is defined %}_{{ param2 }}{% endif %}_{{ param3 }}
params:
param1: value
param2:
type: variable
variable: rougail.unknown_variable
optional: true
param3:
type: information
information: doc
variable: rougail.my_calculated_variable
```
Un exemple avec un paramètre de type suffix :
```yml
---
version: '1.0'
varname:
multi: true
default:
- val1
- val2
my_dyn_family_:
type: dynamic
variable: rougail.varname
description: 'Describe '
my_dyn_var:
type: string
default:
type: jinja
jinja: 'the suffix is: {{ param1 }}'
params:
param1:
type: suffix
```
Dans cette exemple, on voit une famille dynamique. Deux familles vont être crées : `rougail.my_dyn_family_val1.my_dyn_var` et `rougail.my_dyn_family_val2.my_dyn_var`.
La valeur de la variable à l'intérieur de cette famille 'this suffix is: ' + la valeur du suffix (respectivement `val1` et `val2`).
Un exemple avec un paramètre de type index :
```yml
---
version: '1.0'
family:
type: leadership
leader:
multi: true
default:
- val1
- val2
follower1:
default:
type: jinja
jinja: 'the index is: {{ param1 }}'
params:
param1:
type: index
```
### Calcul via une variable
Copier une variable dans une autre :
```yml
---
version: '1.0'
my_variable:
multi: true
default:
- val1
- val2
my_calculated_variable:
multi: true
default:
type: variable
variable: rougail.my_variable
```
Copier une variable dans une autre si la source n'a pas de problème de propriété :
```yml
---
version: '1.0'
my_variable:
default: val1
disabled: true
my_calculated_variable:
multi: true
default:
type: variable
variable: rougail.my_variable
propertyerror: false
```
Copier deux variables non multiple dans une variable multiple :
```yml
---
version: '1.0'
my_variable_1:
default: val1
my_variable_2:
default: val2
my_calculated_variable:
multi: true
default:
- type: variable
variable: rougail.my_variable_1
- type: variable
variable: rougail.my_variable_2
```
Une variable dans une famille dynamique peut également être utilisé dans un calcul.
Par exemple en utilisant la variable pour un suffixe particulier :
```yml
---
version: '1.0'
varname:
multi: true
default:
- val1
- val2
my_dyn_family_:
type: dynamic
variable: rougail.varname
description: 'Describe '
my_dyn_var:
type: string
default:
type: suffix
all_dyn_var:
default:
type: variable
variable: rougail.my_dyn_family_val1.my_dyn_var
```
Dans ce cas, on récupère la valeur `val1`.
Deuxième exemple en utilisant la variable pour tous les suffixes :
```yml
---
version: '1.0'
varname:
multi: true
default:
- val1
- val2
my_dyn_family_:
type: dynamic
variable: rougail.varname
description: 'Describe '
my_dyn_var_:
type: string
default:
type: suffix
all_dyn_var:
multi: true
default:
type: variable
variable: rougail.my_dyn_family_.my_dyn_var_
```
Dans ce cas, on récupère la liste `val1` et `val2`.
### Calcul via un suffix
```yml
---
version: '1.0'
varname:
multi: true
default:
- val1
- val2
my_dyn_family_:
type: dynamic
variable: rougail.varname
description: 'Describe '
my_dyn_var_:
type: string
default:
type: suffix
```
### Calcul via un index
```yml
---
version: '1.0'
family:
type: leadership
leader:
multi: true
default:
- val1
- val2
follower1:
type: number
default:
type: index
```
### Rédéfinition
Dans un premier dictionnaire déclarons notre variable et notre calcule :
```yml
---
version: '1.0'
my_calculated_variable:
default:
type: jinja
jinja: 'the value is calculated'
```
Dans un second dictionnaire il est possible de redéfinir le calcul :
```yml
---
version: '1.0'
my_calculated_variable:
redefine: true
default:
type: jinja
jinja: 'the value is redefined'
```
Dans un troisième on peut même supprimer le calcul :
```yml
---
version: '1.0'
my_calculated_variable:
redefine: true
default: null
```

View file

@ -1,698 +0,0 @@
---
gitea: none
include_toc: true
---
# Construire une liste d'options
Rougail permet de construite des options [Tiramisu](https://forge.cloud.silique.fr/gnunux/tiramisu) à partir de dictionnaire écrit en YAML.
Le principal avantage étant que l'écrire des options est beaucoup plus simple.
Une fois chargé, on retrouve toute la puissance de Tiramisu dans la gestion de la configuration.
Avant de commencer, il faut connaitre les spécificités du format de fichier YAML, des notions de [Jinja](https://jinja.palletsprojects.com/) et de [Tiramisu](https://forge.cloud.silique.fr/gnunux/tiramisu).
# La configuration du proxy type Mozilla Firefox
L'objectif de ce première tutorial est de reproduire cette page de paramètres de Mozilla Firefox :
![Firefox parameters page](firefox.png "Firefox parameters page")
Les variables vont être créées dans plusieurs fichiers dans un but didactique. Bien évidement toutes les variables pourront être mise dans le même fichier.
## La famille proxy
Nous allons classer toutes ces variables dans une famille.
Cette famille s'appelera proxy. Créons le premier fichier dict/00-proxy.yml
```yml
---
version: '1.0'
proxy:
description: Configure Proxy Access to the Internet
type: family
```
Le type de la famille est ici précisé parce qu'on n'a pas de variable ou de famille à l'intérieur de cette famille. Le moteur pensera alors que c'est une simple variable.
## Le type de proxy
Il est possible de définir plusieurs modes de configuration du proxy (de "pas de proxy" à "la configuration d'un fichier de configuration automatique").
Nous allons donc créer une première variable dans cette famille dont la description est "Proxy mode". Créons le deuxième fichier dict/01-proxy\_mode.yml
```yml
---
version: '1.0'
proxy:
proxy_mode:
description: Proxy mode
type: choice
choices:
- No proxy
- Auto-detect proxy settings for this network
- Use system proxy settings
- Manual proxy configuration
- Automatic proxy configuration URL
default: No proxy
mandatory: true
```
Cette variable nécessite une valeur (la valeur `None` n'est pas acceptable), elle est donc obligatoire (`mandatory`).
Si l'utilisateur ne précise pas de valeurs, la valeur de cette variable sera "No proxy" (`default`).
La variable est à choix (`type`: choice) donc la liste des valeurs disponible est contrainte (`choices`), seul les valeurs suivantes sont autorisés :
- No proxy
- Auto-detect proxy settings for this network
- Use system proxy settings
- Manual proxy configuration
- Automatic proxy configuration URL
Testons nos deux premiers dictionnaires :
```python
>>> from rougail import Rougail, RougailConfig
>>> from pprint import pprint
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'No proxy'}
```
## Le mode manuel
Toute la configuration manuelle du proxy sera classé dans une famille. Créons le fichier dict/02-proxy\_manual.yml :
```yml
---
version: '1.0'
proxy:
manual:
description: Manual proxy configuration
type: family
disabled:
type: jinja
jinja: |
{% if rougail.proxy.proxy_mode != 'Manual proxy configuration' %}
the mode proxy is not manual
{% endif %}
```
Si l'utilisateur choisi le mode de proxy "Manual proxy configuration", on veut voir apparaitre (`disabled`) une nouvelle sous-famille appelé manual.
Si le template Jinja renvoi du texte, la famille sera alors désactivé. Sinon elle est accessible.
Désactiver un famille signifie qu'on ne pourra pas y accèder ainsi qu'aux variables ou familles inclusent dans cette famille.
### La configuration du proxy HTTP
Dans cette famille ajoutons une sous-famille `http_proxy` contenant les variables `address` et `port`. Créons le fichier dict/03-proxy\_manual\_http\_proxy.yml :
```yml
---
version: '1.0'
proxy:
manual:
http_proxy:
description: HTTP Proxy
address:
description: HTTP address
type: domainname
mandatory: true
port:
description: HTTP Port
type: port
default: '8080'
```
Les deux variables ont des types particuliers (`domainname` ou `port`) pour valider les valeurs configurer par l'utilisateur.
Pas la peine de préciser le type de la famille `http_proxy` parce qu'on a déclaré les sous-variables dans ce fichier.
### Dupliquer la configuration HTTP vers HTTPS
On veux proposer à l'utilisateur la possiblité de renseigner le même proxy pour les requêtes HTTPS. Créons le fichier dict/04-proxy\_manual\_http\_use\_for\_https.yml :
```yml
version: '1.0'
proxy:
manual:
use_for_https:
description: Also use this proxy for HTTPS
type: boolean
```
Cette variable est de type `boolean`. Sa valeur par défaut est `True`.
### La configuration du proxy HTTPS
Ajoutons une nouvelle sous-famille `ssl_proxy` avec les variables `address` et `port`. Créons le fichier dict/05-proxy\_manual\_ssl\_proxy.yml :
```yml
version: '1.0'
proxy:
manual:
ssl_proxy:
description: HTTPS Proxy
hidden:
type: variable
variable: rougail.proxy.manual.use_for_https
address:
description: HTTPS address
type: domainname
default:
type: jinja
jinja: |
{% if rougail.proxy.manual.use_for_https %}
{{ rougail.proxy.manual.http_proxy.address }}
{% endif %}
mandatory: true
port:
description: HTTPS Port
type: port
default:
type: jinja
jinja: |
{% if rougail.proxy.manual.use_for_https %}
{{ rougail.proxy.manual.http_proxy.port }}
{% endif %}
mandatory: true
```
Suivant la valeur de la variable `rougail.proxy.mandatory.use_for_https` cette famille apparaitra ou disparaitra (`hidden`).
Contrairement à tout à l'heure, il n'est pas nécessaire de passer par une fonction Jinja.
De plus, la famille n'est pas désactivé (`disabled`) parce que les variables devront rester accessible en mode lecture seule.
Les variables `address` et `port` sont copiées de HTTP vers HTTPS si `rougail.proxy.use_for_https` est à True.
Testons différents configuration :
```python
>>> from rougail import Rougail, RougailConfig
>>> from pprint import pprint
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'No proxy'}
```
Pour le moment le proxy n'est pas configuré, donc on ne voit aucune variable.
Regardons ce qui se passe si on accède à l'option description `rougail.proxy.manual` si on n'est pas en mode manuel :
```python
>>> pprint(config.option('rougail.proxy.manual').value.get(), sort_dicts=False)
```
On a bien une erreur (avec le message définit dans le template Jinja) :
```
tiramisu.error.PropertiesOptionError: cannot access to optiondescription "Manual proxy configuration" because has property "disabled" (the mode proxy is not manual)
```
Configurons le proxy en mode manuel :
```python
>>> config.property.read_write()
>>> config.option('rougail.proxy.proxy_mode').value.set('Manual proxy configuration')
>>> config.option('rougail.proxy.manual.http_proxy.address').value.set('proxy.example')
>>> pprint(config.value.get(), sort_dicts=False)
```
Les variable apparaisse bien avec les valeurs voulues :
```json
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
'rougail.proxy.manual.http_proxy.address': 'proxy.example',
'rougail.proxy.manual.http_proxy.port': '8080',
'rougail.proxy.manual.use_for_https': True}
```
Passons en mode `lecture seule` :
```python
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
'rougail.proxy.manual.http_proxy.address': 'proxy.example',
'rougail.proxy.manual.http_proxy.port': '8080',
'rougail.proxy.manual.use_for_https': True,
'rougail.proxy.manual.ssl_proxy.address': 'proxy.example',
'rougail.proxy.manual.ssl_proxy.port': '8080'}
```
En mode `lecture seule`, on voit la configuration HTTPS qui apparait.
De plus on peut remarquer également que les valeurs des variables de `rougail.proxy.manual.http_proxy` ont bien été copié dans `rougail.proxy.manual.ssl_proxy`.
En passant `rougail.proxy.manual.use_for_https` à False, il est possible de modifier la configuration HTTPS :
```python
>>> config.property.read_write()
>>> config.option('rougail.proxy.manual.use_for_https').value.set(False)
>>> config.option('rougail.proxy.manual.ssl_proxy.address').value.set('other.proxy.example')
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
'rougail.proxy.manual.http_proxy.address': 'proxy.example',
'rougail.proxy.manual.http_proxy.port': '8080',
'rougail.proxy.manual.use_for_https': False,
'rougail.proxy.manual.ssl_proxy.address': 'other.proxy.example',
'rougail.proxy.manual.ssl_proxy.port': '8080'}
```
La valeur de la variable `rougail.proxy.manual.ssl_proxy.address` a été modifiée.
Mais si cette variable est à nouveau cachée, la valeur de cette variable reviens à la valeur par défaut :
```python
>>> config.option('rougail.proxy.manual.use_for_https').value.set(False)
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
'rougail.proxy.manual.http_proxy.address': 'proxy.example',
'rougail.proxy.manual.http_proxy.port': '8080',
'rougail.proxy.manual.use_for_https': False,
'rougail.proxy.manual.ssl_proxy.address': 'proxy.example',
'rougail.proxy.manual.ssl_proxy.port': '8080'}
```
### La configuration du proxy SOCKS
Ajoutons une nouvelle sous-famille `socks_proxy` avec les variables `address`, `port` et `version`. Créons le fichier dict/06-proxy\_manual\_socks\_proxy.yml :
```yml
version: '1.0'
proxy:
manual:
socks_proxy:
description: SOCKS Proxy
address:
description: SOCKS Address
type: domainname
port:
description: SOCKS Port
type: port
version:
description: SOCKS host version used by proxy
type: choice
choices:
- v4
- v5
default: v5
```
Rien a signaler pour cette famille et ces variables.
## Le mode détection automatique
Ajoutons une nouvelle sous-variable `auto`. Créons le fichier dict/07-proxy\_auto.yml :
```yml
version: '1.0'
proxy:
auto:
type: web_address
description: Automatic proxy configuration URL
mandatory: true
disabled:
type: jinja
jinja: |
{% if rougail.proxy.proxy_mode != 'Automatic proxy configuration URL' %}
the proxy mode is not automatic
{% endif %}
```
Le type `web_address` impose une valeur qui commence par http:// ou https://.
Cette variable est activée lorsque le proxy est en mode automatique.
## Les exceptions au proxy
Enfin ajoutons une variable contenant les exceptions au proxy. Pour cela créons le fichier dict/07-proxy\_no\_proxy.yml :
```yml
version: '1.0'
proxy:
no_proxy:
description: Address for which proxy will be desactivated
multi: true
type: "domainname"
params:
allow_ip: true
allow_cidr_network: true
allow_without_dot: true
allow_startswith_dot: true
disabled:
type: jinja
jinja: |
{% if rougail.proxy.proxy_mode == 'No proxy' %}
proxy mode is no proxy
{% endif %}
```
C'est une variable de type `domainname` mais qu'on personnalise un peu (`params`), en effet on autorisé :
- les IP
- les réseaux au format CIDR
- les noms de machine (donc sans '.')
- les sous-domaines type .example
Il peut y avoir plusieurs exceptions au proxy, la variable est donc `multi`.
Cette varible n'est pas accessible uniquement si aucun proxy n'est défini (`disabled`).
Pour tester :
```python
>>> from rougail import Rougail, RougailConfig
>>> from pprint import pprint
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.property.read_write()
>>> config.option('rougail.proxy.proxy_mode').value.set('Manual proxy configuration')
>>> config.option('rougail.proxy.manual.http_proxy.address').value.set('proxy.example')
>>> config.option('rougail.proxy.no_proxy').value.set(['.example', '192.168.1.1'])
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
```
```json
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
'rougail.proxy.manual.http_proxy.address': 'proxy.example',
'rougail.proxy.manual.http_proxy.port': '8080',
'rougail.proxy.manual.use_for_https': True,
'rougail.proxy.manual.ssl_proxy.address': 'proxy.example',
'rougail.proxy.manual.ssl_proxy.port': '8080',
'rougail.proxy.manual.socks_proxy.address': None,
'rougail.proxy.manual.socks_proxy.port': None,
'rougail.proxy.manual.socks_proxy.version': 'v5',
'rougail.proxy.no_proxy': ['.example', '192.168.1.1']}
```
Mais pas possible de mettre une valeur invalide :
```python
>>> config.option('rougail.proxy.no_proxy').value.set(['.example', '192.168.1.1', 'not valid'])
[..]
tiramisu.error.ValueOptionError: "not valid" is an invalid domain name for "Address for which proxy will be desactivated", could be a IP, otherwise must start with lowercase characters followed by lowercase characters, number, "-" and "." characters are allowed
```
## La demande d'authentification
Rien de particulier à la création de la demande d'authentification. Pour cela créons le fichier dict/08-proxy\_prompt\_authentication.yml :
```yml
version: '1.0'
proxy:
prompt_authentication:
description: Prompt for authentication if password is saved
type: boolean
default: true
disabled:
type: jinja
jinja: |
{% if rougail.proxy.proxy_mode == 'No proxy' %}
proxy mode is no proxy
{% endif %}
```
## Le DNS du proxy SOCKS v5
La variable DNS pour le proxy SOCKS v5 n'apparait que si le proxy est configuré et que la version du proxy SOCKS sélectionné est bien la "v5".
Créons le fichier dict.09-proxy\_proxy\_dns\_socks5.yml :
```yml
version: '1.0'
proxy:
proxy_dns_socks5:
description: Use proxy DNS when using SOCKS v5
type: boolean
default: false
disabled:
type: jinja
params:
socks_version:
type: variable
variable: rougail.proxy.manual.socks_proxy.version
propertyerror: false
jinja: |
{% if rougail.proxy.proxy_mode == 'No proxy' %}
the proxy mode is no proxy
{% elif socks_version is undefined or socks_version == 'v4' %}
socks version is v4
{% endif %}
```
La difficulté ici c'est que la variable `rougail.proxy.manual.socks_proxy.version` peut être désactivé (et donc non utilisable dans un calcul).
Dans ce cas, nous allons ajouter un paramètre (ici appelé `socks_version`) qui contiendra, s'il n'y a pas d'erreur de propriété, la valeur de la variable.
Sinon le paramètre ne sera pas passé au template Jinja. C'est pourquoi il faut tester dans le template Jinja si la variable `socks_version` existe bien.
## Le DNS à travers HTTPS
Enfin nous allons configurer le DNS à travers HTTPS dans le fichier 10-proxy\_dns\_over\_https.yml :
```yml
version: '1.0'
proxy:
dns_over_https:
description: DNS over HTTPS
enable_dns_over_https:
description: Enable DNS over HTTPS
type: boolean
default: false
provider:
description: Use Provider
type: choice
choices:
- Cloudflare
- NextDNS
- Custom
default: Cloudflare
disabled:
type: jinja
jinja: |
{% if not rougail.proxy.dns_over_https.enable_dns_over_https %}
Enable DNS over HTTPS is False
{% endif %}
custom_dns_url:
description: Custom DNS URL
type: web_address
mandatory: true
disabled:
type: jinja
params:
provider:
type: variable
variable: rougail.proxy.dns_over_https.provider
propertyerror: false
jinja: |
{% if provider is not defined or provider != 'Custom' %}
provider is not custom
{% endif %}
validators:
- type: jinja
jinja: |
{% if rougail.proxy.dns_over_https.custom_dns_url.startswith('http://') %}
only https is allowed
{% endif %}
```
La seule particularitée ici est qu'on a ajouté une validation (`validators`) supplémentaire à la variable `custom_dns_url`. Seul une adresse commençant par https:// est autorisé (pas http://).
# La configuration du proxy type FoxyProxy
Voici maintenant l'intégration d'une partie du plugin Firefox FoxyProxy.
L'idée est d'avoir un espace de nom spécifique à FoxyProxy et de retrouver dedans une partie du paramétrage qu'on aura fait dans l'espace de nom principal.
Voici à quoi ressemble la page :
![FoxyProxy parameters page](foxyproxy.png "FoxyProxy parameters page")
Il est possible, dans ce plugin, de spécifié un nombre illimité de proxy.
Notre famille "proxy" ne sera plus de type `family` comme tout a l'heure mais du type "leadership".
Voici le contenu complet de la configuration du proxy type FoxyProxy à mettre dans le fichier foxyproxy/00-base.yml :
```yml
---
version: '1.0'
proxy:
_type: leadership
title:
description: Title or Description
multi: true
color:
description: Color
mandatory: true
type:
type: choice
choices:
- HTTP
- HTTPS/SSL
- SOCKS5
- SOCKS4
- PAC URL
- WPAD
- System (use system settings)
- Direct (no proxy)
default: Direct (no proxy)
address:
description: IP address, DNS name, server name
multi: true
mandatory: true
disabled:
type: jinja
jinja: |
{% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %}
proxy does not need address
{% endif %}
default:
type: jinja
params:
firefox_address:
type: variable
variable: rougail.proxy.manual.http_proxy.address
propertyerror: false
jinja: |
{% if firefox_address is not undefined %}
{{ firefox_address }}
{% endif %}
port:
description: Port
type: port
mandatory: true
default:
type: jinja
params:
firefox_port:
type: variable
variable: rougail.proxy.manual.http_proxy.port
propertyerror: false
jinja: |
{% if firefox_port is not undefined %}
{{ firefox_port }}
{% endif %}
disabled:
type: jinja
jinja: |
{% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %}
proxy does not need port
{% endif %}
username:
description: Username
type: unix_user
mandatory:
type: jinja
jinja: |
{% if foxyproxy.proxy.password %}
username is mandatory
{% endif %}
disabled:
type: jinja
jinja: |
{% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %}
proxy does not need username
{% endif %}
password:
description: Password
type: secret
disabled:
type: jinja
jinja: |
{% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %}
proxy does not need password
{% endif %}
url:
type: web_address
disabled:
type: jinja
jinja: |
{% if foxyproxy.proxy.type not in ['PAC URL', 'WPAD'] %}
proxy does not need url
{% endif %}
```
Quelques remarques :
- dans la famille meneuse `foxyproxy.proxy` il y a une variable nommée "type", cela peut entrer en conflit avec l'attribute `type`. Dans ce cas, pour spécifier le type on utilise l'attribut `_type`
- une variable suiveuse peut également être multiple (ce qui est le cas de `foxyproxy.proxy.address`)
- `foxyproxy.proxy.username` devient obligatoire si `foxyproxy.proxy.password` est spécifié, en effet un mot de passe sans nom d'utilisateur n'a pas de sens
Testons :
```python
>>> from rougail import Rougail, RougailConfig
>>> from pprint import pprint
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> RougailConfig['extra_dictionaries']['foxyproxy'] = ['foxyproxy/']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.option('rougail.proxy.proxy_mode').value.set('Manual proxy configuration')
>>> config.option('rougail.proxy.manual.http_proxy.address').value.set('proxy.example')
>>> config.option('foxyproxy.proxy.title').value.set(['MyProxy'])
>>> config.option('foxyproxy.proxy.type', 0).value.set('HTTP')
>>> config.option('foxyproxy.proxy.color', 0).value.set('#00000')
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
```
Ce qui donne :
```json
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
'rougail.proxy.manual.http_proxy.address': 'proxy.example',
'rougail.proxy.manual.http_proxy.port': '8080',
'rougail.proxy.manual.use_for_https': True,
'rougail.proxy.manual.ssl_proxy.address': 'proxy.example',
'rougail.proxy.manual.ssl_proxy.port': '8080',
'rougail.proxy.manual.socks_proxy.address': None,
'rougail.proxy.manual.socks_proxy.port': None,
'rougail.proxy.manual.socks_proxy.version': 'v5',
'rougail.proxy.no_proxy': [],
'rougail.proxy.proxy_dns_socks5': False,
'rougail.proxy.dns_over_https.enable_dns_over_https': False,
'foxyproxy.proxy.title': [{'foxyproxy.proxy.title': 'MyProxy',
'foxyproxy.proxy.color': '#00000',
'foxyproxy.proxy.type': 'HTTP',
'foxyproxy.proxy.address': ['proxy.example'],
'foxyproxy.proxy.port': '8080',
'foxyproxy.proxy.username': None,
'foxyproxy.proxy.password': None}]}
```
Le parti pris ici est de rendre obligatoire `foxyproxy.proxy.username` si un mot de passe est spécifié dans la variable `foxyproxy.proxy.password`.
Il est logique d'avoir un nom d'utilisateur sans mot de passe (dans ce cas là le mot de passe sera demander lors de la connexion au proxy).
Mais l'inverse ne l'est pas.
D'un point de vu utilisateur cela peut paraitre perturbant (si on met le mot de passe, il faut revenir a l'option précédent pour préciser le mot de passe).
Il est possible d'inverser la logique.
Si la variable `foxyproxy.proxy.username` est renseignée, la variable `foxyproxy.proxy.password` devient modifiable.
Aucune des deux variables n'a ainsi besoin d'être obligatoire.
Si vous préférez cette option, voici un second dictionnaire extra "foxyproxy/01-redefine.yml" qui va redéfinir le comportement uniquement des variable `foxyproxy.proxy.username` et `foxyproxy.proxy.password` :
```yml
---
version: '1.0'
proxy:
username:
redefine: true
# suppress mandatory constrainte
mandatory: false
password:
redefine: true
hidden:
type: jinja
jinja: |
{% if not foxyproxy.proxy.username %}
no username defined
{% endif %}
```
A vous de jouer maintenant !

View file

@ -1,247 +0,0 @@
---
gitea: none
include_toc: true
---
# Variable
## Synopsis
Une variable est un conteneur qui contiendra une ou plusieurs valeurs.
## Paramètres
| Paramètre | Commentaires |
|-----------|--------------|
| **name**<br/>`string`<br/>`mandatory` | Nom de la variable.<br/>C'est avec ce nom qu'on va pouvoir interagir avec la variable.<br/>Il est préférable de suivre la [convention sur les noms de variable](convention.md). |
| **type**<br/>`string` | Type de la variable.<br/>Ce type définit la validation par défaut de la variable<br/>**Valeur par défaut :** string |
| **params**<br/>`list` | Liste de paramètre permettant d'adapté la validation de type. |
| **description**<br/>`string` | La description de la variable.<br/>Information utilisateur permettant de comprendre l'utilité de la variable.<br/>**Valeur par défaut :** le nom |
| **help**<br/>`string` | Aide complémentaire associée à la variable. |
| **default** | Valeur(s) par défaut de la variable.<br/>Cette valeur est typée, il faut remplir correctement le fichier YAML pour ne pas définir une valeur avec un type incorrect. Par exemple, un "number" doit êre un chiffre, une variable multiple doit être une liste, ...<br/>Pour une variable multiple non [meneuse](../family/leadership.md), la première valeur défini dans la liste sera également la valeur par défaut proposée si on ajoute une nouvelle valeur à cette variable. |
| **validators**<br/>`list` | Validateurs de valeur.<br/>. Liste de template Jinja. La valeur de la variable sera considérée comme invalide si le template retourne quelque chose. |
| **auto_save**<br/>`boolean` | Variable à valeur automatiquement modifiée.<br/>Une variable avec valeur automatiquement modifiée est une variable dont la valeur sera considérée comme modifiée (ce n'est plus une valeur par défaut).<br/>Par exemple, si la valeur de cette variable est issue d'un calcul, la valeur ne sera plus recalculée.<br/>Ces variables sont généralement des variables obligatoires. En effet ces variables ne sont automatiquement modifiées que si elles ont une valeurs.<br/>Une [variable meneuse ou suiveuse](../family/leadership.md) ne peut pas avoir la propriété `auto_save`. |
| **mode**<br/>`string` | Mode de la variable.<br/>**Valeur par défaut :** Le mode par défaut d'une variable est le mode de la famille parente.<br/>Cas particuliers :<br/>- une variable à valeur automatiquement modifiée ou une variable en lecture seule automatique est par défaut en mode "basic"<br/>- si la variable n'est pas dans une famille, la variable aura le mode "normal" par défaut<br/>- une variable obligatoire sans valeur par défaut (calculer ou non) aura le mode "basic" |
| **muli**<br/>`boolean` | La variable a pour valeur une liste. **Valeur par défaut** : false |
| **unique**<br/>`boolean` | La variable multiple accepte plusieurs fois la même valeur. Si unique est à `false` une variable multiple n'accepte qu'une seule fois la même valeur dans la liste. **Valeur par défaut :** false |
| **hidden**<br/>`boolean` ou [`calcul`](../condition/README.md) | Variable invisible.<br/>Permet de cacher une variable.<br/>Cela signifie que la variable ne sera plus visible en lecture écriture, mais uniquement pour des calculs ou en mode lecture seule.<br/>Lorsqu'on rend invisible une variable l'utilisateur ne pourra pas modifier sa valeur, il s'il a déjà réussi à la modifier, cette valeur ne sera pas prise en compte. **Valeur par défaut :** false |
| **disabled**<br/>`boolean` ou [`calcul`](../condition/README.md) | Variable désactivée.<br/>Permet de désactiver une variable.<br/>Cela signifie que la variable ne sera plus visible pour l'utilisateur mais également pour un calcul. **Valeur par défaut :** false |
| **mandatory**<br/>`boolean` ou [`calcul`](../condition/README.md) | Variable obligatoire.<br/>Variable dont une valeur est requise.<br/>Pour une variable multiple, cela signifie que la liste ne doit pas être vide.<br/>📝 Une variable avec une valeur par défaut non calculée ou de type "boolean" sont automatiquement considéré comme obligatoire, si ce n'est pas le comportement voulu, il mettre le préciser comme cela : "mandatory: false". **Valeur par défaut :** false |
| **redefine**<br/>`boolean` | Il est possible de définir une variable dans un dictionnaire et de changer son comportement dans une second dictionnaire. Dans ce cas il faut explicitement redéfinir la variable. **Valeur par défaut :** false|
| **exists**<br/>`boolean` | Cette attribut permet deux choses :<br/>- créer une variable si elle n'existe pas dans un autre dictionnaire (sinon ne rien faire), dans ce cas la valeur de l'attribut doit être `true`<br/>- en conjonction avec l'attribut redefine à la valeur true, ne modifie le comportement si elle est préexistante, dans ce cas la valeur de l'attribut doit être `false`.<br/>**Valeur par défaut :** null |
| **test**<br/>`list` | L'attribut "test" est un attribut spécial qui permet aux concepteurs d'un dictionnaire d'influencer un robot de test en précisant de valeurs utiles à tester.<br/>Concrêtement, le contenu de cet attribut est enregister dans une "information" de l'option Tiramisu correspondante. |
## Les types des variables
Une variable a un type.
Ce type permet de définir les valeurs acceptées par cette variable.
| valeur | Commentaires | Paramètres | Exemples |
|--------|--------------|------------|----------|
| string | chaine de caractère (type par défaut) | | test<br/>"1"<br/>"true" |
| number | un nombre | `min_number` : nombre minimum autorisé<br/>`max_number` : nombre maximal autorisé | 1 |
| float | un chiffre flottant | | 1.2 |
| boolean | Un booléen, si aucune valeur n'est défini la valeur par défaut de cette variable sera "true", la variable sera également obligatoire par défaut | | true<br/>false |
| secret | un secret (comme un mot de passe, une clef privée, ...) | | hO_'hi |
| mail | une adresse mail | | test@rougail.example |
| filename | nom de fichier au sens Unix | | /etc/passwd |
| date | une date au format "%Y-%m-%d" | | 2021-01-30
| unix\_user | nom d'utilisateur au sens Unix | | test |
| ip | n'importe quelle adresse IPv4 | `private_only` : seule des IP privée (false par défaut)<br/>`allow_reserved` : autorise les IP réservées (true par défaut) | 1.2.3.4 |
| cidr | n'importe quelle adresse IPv4 au format CIDR | `private_only` : seule des IP privée (false par défaut) `allow_reserved` : autorise les IP réservées (false par défaut) | 1.2.3.4/24 |
| netmask | masque d'une adresse IPv4 | | 255.255.255.0 |
| network | adresse réseau | | 192.168.1.0 |
| network\_cidr | adresse réseau au format CIDR | | 192.168.1.0/24 |
| broadcast | adresse de diffusion | | 255.255.255.255 |
| netbios | nom netbios | | machine |
| domainname | nom de domaine | `allow_ip` : autorise une IP plutôt qu'un nom de domaine (false par défaut)<br/>`allow_cidr_network` : autorise une adresse réseau de type CIDR (false par défaut)<br/>`allow_without_dot` : autorise les noms sans point (false par défault)<br/>`allow_startswith_dot` : autorise de commencer par un point (false par défault) | rougail.example |
| hostname | nom d'hôte | `allow_ip` : autorise une IP plutôt qu'un nom de domaine (false par défaut) | machine |
| web\_address | adresse web | `allow_ip` : autorise une IP plutôt qu'un nom de domaine (false par défaut)<br/>`allow_without_dot` : autorise les noms sans point (true par défault) | http://rougail.example |
| port | port | `allow_range` : autorise un interval de port, par exemple 80:85 (false par défaut)<br/>`allow_zero` : autorise le port 0 (false par défaut)<br/>`allow_wellknown` : autorise les ports de 1 à 1023 (true par défaut)<br/>`allow_registred` : autorise les ports de 1024 à 49151 (true par défaut) `allow_private` : autorise les ports supérieurs à 49152 (true par défaut)<br/>`allow_protocol` : autorise l'ajout du protocole, par exemple tcp:80 (false par défaut) | 8080 |
| mac | adresse MAC | | 11:11:11:11:11:11 |
| unix\_permissions | droit d'accès au fichier, répertoire, ... | | 644 |
| choice | variable à choix | | |
## Les modes des variables
Par défault, il existe trois "mode" dans Rougail :
- basic : variables indispensables à la mise en place d'un service
- normal : variables couramment modifié par l'utilisateur
- expert : variables a manipuler avec précausion et en toutes connaissances de cause
Il est possible de personnaliser les modes dans la [configuration de rougail](../dev/config.md)
## Exemples
### Quelques variables de base
```yml
---
version: '1.0'
my_variable:
type: string
description: This is a good variable
help: This is the help of a good variable
default: value
my_variable_multi_1:
multi: true
default:
- value
my_variable_multi_2:
multi: true
default:
- value1
- value2
version: '1.0'
my_variable_multi_not_unique:
multi: true
unique: false
default:
- value1
- value2
- value2
```
### Redéfinition d'une variable
Créons notre variable :
```yml
---
version: '1.0'
my_variable:
type: string
```
Et redéfinisons là :
```yml
---
version: '1.0'
my_variable:
redefine: true
description: New description
```
### Créer une variable inexistante
```yml
---
version: '1.0'
my_variable:
exists: false
```
## Redéfinir une variable si elle existe
Parfois on veut pouvoir redéfinir une variable mais seulement dans le cas où elle existe déjà :
```yml
---
version: '1.0'
my_variable:
exists: true
hidden: true
```
Dans ce cas la variable ne sera pas créé mais uniquement cachée si elle existe déjà.
## Une variable à choix
Il est possible d'imposer une liste de valeur pour une variable particulière :
```yml
---
version: '1.0'
my_variable:
type: choice
choices:
- val1
- val2
- val3
```
Dans ce cas, seules les valeurs proposées sont possibles pour cette variable.
Cette variable n'est pas obligatoire donc il est possible de mettre la valeur "None".
Si la variable est obligatoire ou si une valeur est précisée (la variable passe obligatoire) alors la valeur "None" n'est plus autorisé :
```yml
---
version: '1.0'
my_variable:
type: choice
choices:
- val1
- val2
- val3
default: val1
```
Une variable à choix est typée :
Les choix sont typés :
```yml
---
version: '1.0'
my_variable:
type: choice
choices:
- val1
- 3
- true
default: val1
```
dans ce cas, le chiffre 3 est autorisé mais pas la chaine de caractère "3".
## Un variable à choix provenant de variable
Les choix d'une variable peuvent provenir d'une autre variable multiple :
```yml
---
version: '1.0'
source_variable:
multi: true
default:
- val1
- val2
my_variable:
type: choice
choices:
type: variable
variable: rougail.source_variable
```
Dans ce cas, toutes les valeurs de la variable ou des variables seront des choix utilisables par l'utilisateur.
## Un variable à choix provenant d'un template Jinja
Faisons d'abord une fonction `trange` dans le fichier functions.py :
```python
def trange(min, max):
return range(min, max)
```
On va récupérer ici les valeurs de 0 à 9 :
```yml
---
version: '1.0'
my_variable:
type: choice
default: 9
choices:
type: jinja
jinja: |
{% for item in trange(0, 10) %}
{{ item }}
{%- endfor %}
return_type: number
```

4
docs/_static/css/custom.css vendored Normal file
View file

@ -0,0 +1,4 @@
.wy-table-responsive table td {
white-space: normal;
}

2
docs/bribes.txt Normal file
View file

@ -0,0 +1,2 @@
variable
<https://en.wikipedia.org/wiki/Variable_(computer_science)>`_

286
docs/check.rst Normal file
View file

@ -0,0 +1,286 @@
Verification function
==========================
Synopsis
-------------
A verification is a complementary validation to the type which allows the content of a variable to be validated more precisely.
A :term:`validator` is necessarily a Jinja type calculation.
Parameters
--------------
Depending on the types of calculation, the parameters will be different:
.. list-table::
:widths: 15 25 20 15
:header-rows: 1
* - **Calculation type**
- **Parameter**
- **Comments**
- **Sample**
* -
- **type**
`string`
`mandatory`
- Type of calculation, the only possible value is: jinja
- jinja
* - **jinja**
`string`
`mandatory`
- Jinja template
- {% if rougail.variable == 'not_allowed' %}not allowed!{% endif %}
-
* - **params**
`list`
- Additional parameters passed to the Jinja template
-
-
There are two types of parameter:
- the standard parameters (string, boolean, integer, null), in this case just do: "key: value"
- advanced settings:
- parameter via a variable
- parameter via an information
- parameter via a suffix: in the case of a variable in a dynamic family
- parameter via an index: in the case of a :term:`follower` variable
.. list-table::
:widths: 15 25 20 15
:header-rows: 1
* - **Parameter's type**
- **Parameter**
- **Comments**
- **Sample**
* -
- **name**
`string`
`mandatory`
- Parameter's name
- my_param
* -
- **type**
`string`
`mandatory`
- Type of parameter, possible values are: variable, information, suffix or index
- suffix
* - Variable
- **variable**
`string`
`mandatory`
- variable's name
- rougail.variable
* - Variable (`mandatory`) information
- **propertyerror**
`boolean`
- If access to the variable is not possible due to a property
(for example `disabled`) by default an error is returned.
If the attribute is `False`, the parameter is not passed to the Jinja template.
**Default value**: `True`
- True
* - Information
- **information**
`string`
`mandatory`
- Name of the information whose value we want to retrieve.
- doc
Samples
--------------
Strict verification of values
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is a simple example of validating values:
.. code-block:: yaml
---
version: '1.0'
my_variable:
validators:
- type: jinja
jinja: |
{% if rougail.my_variable and not rougail.my_variable.islower() %}
{{ rougail.my_variable }} is not lowercase string
{% endif %}
A verification function must take into account 2 important aspects:
- the value may not be entered (even if the variable is mandatory), the None value must be taken into account
- if the value is invalid, a sentence must be returned with an explicit message.
From now on only `None` and lowercase values will be allowed.
Checking values with warning
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In the constraint, it is possible to specify the error level and put it as a warning:
.. code-block:: yaml
---
version: '1.0'
my_variable:
validators:
- type: jinja
jinja: |+
{% if rougail.my_variable and not rougail.my_variable.islower() %}
{{ rougail.my_variable }} is not lowercase string
{% endif %}
params:
warnings_only: true
In this case a value with a capital letter will be accepted, but a warning message will appear.
Verification with parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: yaml
---
version: '1.0'
my_hidden_variable:
disabled: true
my_variable:
validators:
- type: jinja
jinja: |
{% if param1 is defined and rougail.my_variable == param1 %}
has same value as rougail.unknown_variable
{% endif %}
{% if param2 is defined and rougail.my_variable == param2 %}
has same value as rougail.my_hidden_variable
{% endif %}
params:
param1:
type: variable
variable: rougail.unknown_variable
optional: true
param2:
type: variable
variable: rougail.my_hidden_variable
propertyerror: false
An example with a suffix type parameter:
.. code-block:: yaml
---
version: '1.0'
varname:
multi: true
default:
- val1
- val2
my_dyn_family_:
type: dynamic
variable: rougail.varname
description: 'Describe '
my_dyn_var:
type: string
validators:
- type: jinja
jinja: |
{% if rougail.my_dyn_family_.my_dyn_var == param1 %}
forbidden!
{% endif %}
params:
param1:
type: suffix
In this example, we see a dynamic family. Two families will be created: `rougail.my_dyn_family_val1.my_dyn_var` and `rougail.my_dyn_family_val2.my_dyn_var`.
The value of the variable within this family cannot be equal to the value
of the suffix (`val1` and `val2` respectively).
An example with an index type parameter:
.. code-block:: yaml
---
version: '1.0'
family:
type: leadership
leader:
multi: true
default:
- val1
- val2
follower1:
type: number
validators:
- type: jinja
jinja: |
{% if rougail.family.follower1 == param1 %}
forbidden!
{% endif %}
params:
param1:
type: index
Redefinition
---------------
In a first dictionary, let's declare our variable and its verification function:
.. code-block:: yaml
---
version: '1.0'
my_variable:
validators:
- type: jinja
jinja: |
{% if rougail.my_variable and not rougail.my_variable.islower() %}
{{ rougail.my_variable }} is not lowercase string
{% endif %}
In a second dictionary it is possible to redefine the calculation:
.. code-block:: yaml
---
version: '1.0'
my_variable:
redefine: true
validators:
- type: jinja
jinja: |
{% if rougail.my_variable and ' ' in rougail.my_variable %}
{{ rougail.my_variable }} has a space
{% endif %}
In this case only this validator will be executed.
Here is a third dictionary in which we remove the validation:
.. code-block:: yaml
---
version: '1.0'
my_variable:
redefine: true
validators:

240
docs/condition.rst Normal file
View file

@ -0,0 +1,240 @@
Calculated properties
==========================
Synopsis
------------
Calculated properties allow you to add or remove properties to a :term:`variable`
or a :term:`family` depending on the context.
Here is the list of editable properties:
.. list-table::
:widths: 15 15 25
:header-rows: 1
* - **Attribute applicable on**
- **Property's name**
- Comment
* - Variable
Family
- hidden
- Hides a variable or a family, in this case it is not accessible in `read-write` mode,
but remains accessible in a calculation or in `read-only` mode
* - Variable
Family
- disabled
- Deactivates a variable or family, in this case it is never accessible
* - Variable
- mandatory
- The variable expects a value other than `None` or `[]` for multiple variables
A property can be calculated. In this case we have two possibilities:
- calculation via Jinja
- calculation via a variable
Parameters
---------------
.. list-table::
:widths: 15 25 20 15
:header-rows: 1
* - **Calculation type**
- **Parameter**
- **Comment**
- **Sample**
* -
- **type**
`string`
`mandatory`
- Calculation type, possible values are: jinja, variable, information, suffix or index
- jinja
* - Jinja
- **jinja**
`string`
`mandatory`
- Jinja template . For a multiple variable, each line represents a value.
- {% if rougail.variable %}
{{ rougail.variable }}
{% endif %}
* - Jinja
- **params**
`list`
- Additional parameters passed to the Jinja template
-
* - Variable
- **variable**
`string`
`mandatory`
- Name of the associated variable.
.. attention:: The variable must be of `boolean` type.
- rougail.variable
* - Variable
- **propertyerror**
`boolean`
- If access to the variable is not possible due to a property
(for example `disabled`) by default an error is returned.
If the attribute is `False`, the calculated value is False.
**Default value**: `True`
- False
In the case of a Jinja type calculation, it is possible to have parameters.
There are two types of parameter:
- the standard parameters (string, boolean, integer, null), in this case just do: "key: value"
- advanced settings:
- parameter via a variable
- parameter via information
- parameter via a suffix: in the case of a variable in a dynamic family
- parameter via an index: in the case of a follower variable
.. list-table::
:widths: 15 25 20 15
:header-rows: 1
* - **Parameter's type**
- **Parameter**
- **Comments**
- **Sample**
* -
- **name**
`string`
`mandatory`
- parameter's name
- my_param
* -
- **type**
`string`
`mandatory`
- Parameter's type, possible values are: variable, information, suffix or index
- suffix
* - Variable
- **variable**
`string`
`mandatory`
- variable's name
- rougail.variable
* - Variable (`mandatory`) Information
- **propertyerror**
`boolean`
- If access to the variable is not possible due to a property (for example `disabled`) by default an error is returned. If the attribute is False, the parameter is not passed to the Jinja template.
**Default value**: `True`
- False
* - Variable
- **optional**
`boolean`
- The variable may not exist depending on YAML file imports. If the optional parameter is True, the parameter will simply be deleted if the variable does not exist.
**Default value**: `False`
- True
* - information
- **information**
`string`
`mandatory`
- Name of the information whose value we want to retrieve.
- doc
Samples
------------
A Jinja-type calculated property
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is possible to write the condition in Jinja:
.. code-block:: yaml
---
version: '1.0'
condition:
default: 'do not hide!'
my_variable:
hidden:
type: jinja
jinja: |
{% if rougail.condition and rougail.condition == "hide!" %}
this rougail.condition value is 'hide!'
{% endif %}
In this case the variable is hidden if the value of the variable "rougail.condition" is `hide!` and it did not hide for any other value. Be careful, always take into consideration that "rougail.condition" can be equal to `None`.
The message returned by the function is visible in the error message in the event of an access problem:
.. code-block:: python
>>> from rougail import Rougail, RougailConfig
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.property.read_write()
[..]
tiramisu.error.PropertiesOptionError: cannot access to option "my_variable" because has property "hidden" (this rougail.condition value is 'hide!')
It is possible to use parameters when calculating properties as for calculating the `default` attribute.
A calculated property of variable type
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A variable can therefore be calculated via the result of another variable. Please note, this other variable must be of `boolean` type:
.. code-block:: yaml
---
version: '1.0'
condition:
type: boolean
my_variable:
hidden:
type: variable
variable: rougail.condition
If the value of the variable "rougail.condition" is `True` then the variable "rougail.my_variable" is hidden.
Redefintion
~~~~~~~~~~~~~~~~~
It may be that in a dictionary we decide to define a condition.
To delete the calculation from a variable, simply do in a new dictionary:
.. code-block:: yaml
---
version: '1.0'
my_variable:
redefine: true
hidden:

149
docs/conf.py Executable file
View file

@ -0,0 +1,149 @@
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = 'Rougail'
copyright = '2019-2023, Silique'
author = 'gwen'
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = '1.0'
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.extlinks', 'sphinx_lesson',
#'myst_parser', 'sphinx.ext.extlinks'
]
#
#myst_enable_extensions = [
# "amsmath",
# "attrs_inline",
# "colon_fence",
# "deflist",
# "dollarmath",
# "fieldlist",
# "html_admonition",
# "html_image",
## "linkify",
# "replacements",
# "smartquotes",
# "strikethrough",
# "substitution",
# "tasklist",
#]
# **extlinks** 'sphinx.ext.extlinks',
# enables syntax like :proxy:`my source <hello>` in the src files
extlinks = {'proxy': ('/proxy/%s.html',
'external link: ')}
default_role = "code"
html_theme = "sphinx_rtd_theme"
pygments_style = 'sphinx'
html_short_title = "Rougail"
html_title = "Rougail documenation"
# If true, links to the reST sources are added to the pages.
html_show_sourcelink = False
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
html_show_sphinx = False
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
html_show_copyright = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
#source_suffix = ['.rst', '.md']
source_suffix = '.rst'
#source_suffix = {
# '.rst': 'restructuredtext',
# '.txt': 'restructuredtext',
# '.md': 'markdown',
#}
# The master toctree document.
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'en'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
#html_theme = 'alabaster'
# **themes**
#html_theme = 'bizstyle'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
def setup(app):
app.add_css_file('css/custom.css')

76
docs/configuration.rst Normal file
View file

@ -0,0 +1,76 @@
Customizing Rougail's configuration
=======================================
The `Rougail`\ 's configuration is located in the `RougailConfig` object:
.. code-block:: python
from rougail import RougailConfig
It's just a python dictionary with different keys.
To modify it, just do like with any python dictionary:
.. code-block:: python
RougailConfig[key] = value
Configuring the dictionnaries loading
-----------------------------------------
Setting the dictionnaries folders
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are two types of dictionary directories:
- the main dictionaries with the `dictionaries_dir` key. The default setting is `['/srv/rougail/dictionaries']`. This setting shall list the directories folders containing dictionaries files.
- the extra dictionaries with the `extra_dictionaries` key. The value is a dictionary with all namespaces. The key being the namespace and the value being a directory listing.
For example, to add the extra example you must do:
.. code-block:: python
RougailConfig['extra_dictionaries']['example'] = ['/dir1', '/dir2']
Dictionaries are loaded in the same order as the main dictionaries.
The functions file
~~~~~~~~~~~~~~~~~~~~~~~
The file which contains the custom functions is managed in the `functions_file` key and has the default value `/srv/rougail/functions.py`. This key can contain a list if there are several files.
.. important:: Functions must return a value, even if the variable being calculated is a :term:`multiple` variable. If the function can return a multiple value (a list), you must put the name of the function in the `multi_functions` key.
The `auto_freeze` variable
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The `auto_freeze` property is only applied until a specific variable becomes `True`. By default the variable name is `instantiated_module`, but it is possible to change the name of this variable via the `auto_freeze_variable` key.
Modes
~~~~~~~~
.. glossary::
mode
modes are views on variables.
Modes are customizable in Rougail. By default the modes are `basic`, `standard` and `advanced`. It is possible to change this list via the `modes_level` key.
If you change these values, consider changing the default modes of families and variables in your dictionaries.
Default mode for a family
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The default mode for a family is `basic`. It is possible to change the default mode of a family via the `default_family_mode` key.
Default mode for a variable
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The default mode for a variable is `standard`. It is possible to change the default mode of a variable via the `default_variable_mode` key.
Internal functions names
~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is possible to add internal functions via the `internal_functions` key.

16
docs/developer.rst Normal file
View file

@ -0,0 +1,16 @@
Developer notes
==========================
.. admonition:: team developer material
This section is intended to be usefull for team developers only.
Quick installation process
---------------------------------------
This process describes how to install and run the project locally, e.g. for development purposes.
*Nota*: command is to be executed through the terminal
`pip install rougail`

19
docs/dict_convention.rst Normal file
View file

@ -0,0 +1,19 @@
Dictionary conventions
=========================
Dictionary file naming convention
------------------------------------
The order of dictionaries is important for the order in which variables and families are created.
The files must therefore be started with two numbers followed by a hyphen.
For example: `00-base.xml`
Naming convention for families and variables
-----------------------------------------------
The only restriction on the name of families and variables is that the name must not start with the `"_"` (undescore) character.
However, it is preferable to only use lowercase ASCII letters, numbers and the `"_"` (undescore) character.
The snake case typographic convention is therefore used.

36
docs/dictionary.rst Normal file
View file

@ -0,0 +1,36 @@
The dictionaries
=====================
What do you mean by :term:`dictionary`?
-------------------------------------------
A :term:`dictionary` is a YAML file whose structure is described in this documentation page.
A dictionary contains a set of variables loaded into :term:`Tiramisu`, usable at any time, especially in a :term:`templates`.
:term:`Families` and :term:`variables` can be defined in several dictionaries. These dictionaries are then aggregated.
Dictionaries are loaded in the directory order defined by the `dictionaries_dir` configuration parameter.
Each directory is loaded one after the other.
Inside these directories the YAML files will be classified in alphabetical order.
There is no alphabetical ordering of all YAML files in all directories.
It is also possible to :term:`redefine` elements to change the behavior of a family or a variable.
The default namespace
-------------------------
The families and variables contained in these dictionaries are ordered, by default, in the `rougail` namespace. It is possible to change the name of this namespace :doc:`with the `variable_namespace` parameter of the configuration <configuration>`.
This namespace is a bit special, it can access variables in another namespace.
The extra dictionaries
---------------------------
An extra is a different namespace. The idea is to be able to classify the variables by theme.
Extra namespaces must be declared :doc:`when configuring Rougail <configuration>`.
In this namespace we cannot access variables from another `extra` namespace.
On the other hand, it is possible to access the variable of the default namespace.

223
docs/family.rst Normal file
View file

@ -0,0 +1,223 @@
A family
============
Synopsis
---------
A family is a container of variables and subfamily.
.. attention:: A family without a subfamily or subvariable will be automatically deleted.
Name
-------------
It is with this name that we will be able to interact with the family.
It's best to follow the :ref:`convention on variable names`.
Parameters
---------------
.. FIXME: faire une page sur la "convention on variable names"
.. list-table::
:widths: 15 45
:header-rows: 1
* - Parameter
- Comments
* - type, _type
`string`
- possile values:
- `family` (**default value**)
- `leadership`
- `dynamic`
.. note:: If a subfamily or a subvariable already has the name `"type"`, it is possible to use the `"_type"` attribute.
* - description, _description
`string`
- Description of the family.
User information to understand the usefulness of the family.
..note:: If a subfamily or subvariable already has the name "description" it is possible to use the "_description" attribute.
* - help, _help
`string`
- Additional help associated with the family.
.. note:: If a subfamily or a subvariable already has the name "help" it is possible to use the "_help" attribute.
* - mode, _mode
`string`
- Family mode.
The default mode of a family is the smallest mode of the parent families, child variables, or child families that are contained in that family.
This mode also allows you to define the default mode for variables or families included in this family.
.. note:: If a subfamily or a subvariable already has the name "mode" it is possible to add the "_mode" attribute.
* - hidden, _hidden
`string`
- Invisible family.
Allows you to hide a family as well as the variables or families included in this family.
This means that the family will no longer be visible in `read-write` mode, but only for calculations or in `read-only` mode.
.. note:: If a subfamily or a subvariable already has the name "hidden" it is possible to add the "_hidden" attribute.
* - disabled, _disabled
`string`
- Disabled family.
Allows you to deactivate a family as well as the variables or families included in this family.
This means that the family will no longer be visible to the user but also to a :term:`calculation`.
.. note:: If a subfamily or a subvariable already has the name "disabled" it is possible to use the "_disabled" attribute.
Dynamically created family
-----------------------------
To create a family dynamically, you must create a fictitious family linked to a variable.
The family name and description will actually be the prefix of the new name / description.
The suffix will come from the value of the bound variable.
The name of the families and variables it contains will be preserved, however, the description will be the prefix of the real description.
Obviously if the content of the linked variable were to evolve, new dynamic families will appear or disappear.
To note that:
- the variable linked to the family must be a multiple variable
- it is not possible to put a simple family into a dynamic family
- it is possible to put a leading family into a dynamic family
Leader or foller variable
-----------------------------
A leader family has a typical attribute of “leadership”. The type is required.
A leader family
----------------
The leader and follower variables are placed in a leader family.
A leader family cannot contain other families.
The default mode of the leader family is the mode of the leader variable.
Leader variable
----------------
A leader variable is a variable that will guide the length of other variables (called follower variables).
A leader variable is a :doc:`variable` that must have the `multiple` type.
A leader variable may be mandatory.
The default mode corresponds to the smallest mode defined for the follower variables.
Follower variable
--------------------
A follower variable is a variable whose length is not determined by itself, but is identical to that of the leader variable on which it depends.
A follower variable is a variable placed just behind a leader variable or another follower variable.
The order in which the tracking variables are defined is important.
This variable can be of multiple type. In this case, for a determined index of the leading variable, it is possible to put several values to the same variable.
A follower variable may be required. This means that when a leader variable is entered, the follower variable must also be a value at the index considered. If no value is defined for the leader variable, no value is specified for the follower variable.
The default mode of a follower variable corresponds to the mode of the leader variable.
If a leader variable is hidden or disabled, the follower variables will be hidden or disabled as well.
Examples
----------
Simple family
.. code-block:: yaml
---
version: '1.0'
my_family:
type: family
description: This is a great family
help: This is the help of a great family
mode: expert
Dynamically created family
----------------------------
.. code-block:: yaml
---
version: '1.0'
varname:
multi: true
default:
- val1
- val2
my_dyn_family_:
type: dynamic
variable: rougail.varname
description: 'Describe '
my_dyn_var:
type: string
description: 'Variable description for '
This will dynamically create two families:
- "rougail.my_dyn_family_val1" with the description "Describe val1"
- "rougail.my_dyn_family_val2" with the description "Describe val2"
In the dynamic family "rougail.my_dyn_family_val1" we will find a variable "my_dyn_var" with the description "Variable description for val1".
Leader or follower variable
-------------------------------
Definition of leader and follower variables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is an example of defining a leading variable and two following variables:
.. code-block:: yaml
---
version: '1.0'
family:
type: leadership
leader:
multi: true
follower1:
follower2:
multi: true
Adding a new follower variable
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To add a new follower variable, in a new dictionary, simply define one or more new variables in the leader family:
.. code-block:: yaml
---
version: '1.0'
family:
follower3:

478
docs/fill.rst Normal file
View file

@ -0,0 +1,478 @@
Calculated default values
==============================
Synopsis
-----------
A value can be calculated. In this case we have four possibilities:
- calculation via Jinja
- calculation via a variable
- calculation via information
- calculation via a suffix: in the case of a variable in a dynamic family
- calculation via an index: in the case of a follower variable
If the user modifies the value of the variable, the default value is no longer used, so the calculation is no longer carried out. This is also the case if the variable has the `auto_save` attribute.
On the other hand, if the variable is hidden (with the `hidden` parameter), it is the default value that is used and not the value customized by the user.
.. note:: A follower variable cannot be calculated automatically.
Parameters
--------------
Depending on the types of calculation, the parameters will be different:
.. list-table::
:widths: 15 25 20 15
:header-rows: 1
* - Calculation type
- Parameter
- Comments
- Sample
* -
- **type**
`string`
`mandatory`
- Type of calculation, possible values are: jinja, variable, information, suffix or index
- jinja
* - Jinja
- **jinja**
`string`
`mandatory`
- Template Jinja. For a multiple variable, each line represents a value.
- `{% if rougail.variable %}
{{ rougail.variable }}
{% endif %}`
* - Jinja
- **params**
`list`
- Additional parameters passed to the Jinja template
-
* - Variable (`mandatory`)
Information
- **variable**
`string`
- Name of associated variable
- rougail.variable
* - Variable
- **propertyerror**
`boolean`
- If access to the variable is not possible due to a property (for example `disabled`) by default an error is returned. If the attribute is `false`, the calculated value is empty.
**Default value:** `true`
- false
* - Information
- **information**
`string`
`mandatory`
- Name of the information whose value we want to retrieve.
- doc
In the case of a Jinja type calculation, it is possible to have parameters.
There are two types of parameter:
- the standard parameters (string, boolean, integer, null), in this case just do: "key: value"
- the advanced settings:
- parameter via a variable
- parameter via an information
- parameter via a suffix: in the case of a variable in a dynamic family
- parameter via an index: in the case of a follower variable
.. list-table::
:widths: 15 25 20 15
:header-rows: 1
* - Parameter type
- Parameter
- Comments
- Sample
* -
- **name**
`string`
`mandatory`
- parameter's name
- my_param
* -
- **type**
`string`
`mandatory`
- parameter's type, possible values are: variable, information, suffix or index
- suffix
* - Variable
- **variable**
`string`
`mandatory`
- Variable's name
- rougail.variable
* - Variable (`mandatory`) information
- **propertyerror**
`boolean`
- If access to the variable is not possible due to a property (for example `disabled`) by default an error is returned. If the attribute is `False`, the parameter is not passed to the Jinja template.
- **Default value**: `True`
* - Variable
- **optional**
`boolean`
- The variable may not exist depending on YAML file imports.
If the optional parameter is `True`, the parameter will simply be deleted if the variable does not exist.
Default value : `False`
- True
* - Information
- **information**
`string`
`mandatory`
- Name of the information whose value we want to retrieve.
- doc
Examples
-----------
Calculation via a Jinja template
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Let's start with an example from a simple Jinja template:
.. code-block:: yaml
---
version: '1.0'
my_calculated_variable:
default:
type: jinja
jinja: 'no'
Here is a second example with a boolean variable:
.. code-block:: yaml
---
version: '1.0'
my_calculated_variable:
type: boolean
default:
type: jinja
jinja: 'false'
And a multiple value of the number type:
.. code-block:: yaml
---
version: '1.0'
my_calculated_variable:
type: number
multi: true
default:
type: jinja
jinja: |
1
2
3
Let's create a variable whose value is returned by a python function:
.. code-block:: yaml
---
version: '1.0'
my_calculated_variable:
default:
type: jinja
jinja: '{{ return_no() }}'
Then let's create the `return_no` function:
.. code-block:: python
def return_no():
return 'no'
An example with parameters:
.. code-block:: yaml
---
version: '1.0'
my_calculated_variable:
description: my description
default:
type: jinja
jinja: |
{{ param1 }}{% if param2 is defined %}_{{ param2 }}{% endif %}_{{ param3 }}
params:
param1: value
param2:
type: variable
variable: rougail.unknown_variable
optional: true
param3:
type: information
information: doc
variable: rougail.my_calculated_variable
An example with a `suffix` type parameter:
.. code-block:: yaml
---
version: '1.0'
varname:
multi: true
default:
- val1
- val2
my_dyn_family_:
type: dynamic
variable: rougail.varname
description: 'Describe '
my_dyn_var:
type: string
default:
type: jinja
jinja: 'the suffix is: {{ param1 }}'
params:
param1:
type: suffix
In this example, we see a dynamic family. Two families will be created: `rougail.my_dyn_family_val1.my_dyn_var` and `rougail.my_dyn_family_val2.my_dyn_var`.
The value of the variable inside this family 'this suffix is: ' + the value of the suffix (`val1` and `val2` respectively).
An example with an index type parameter:
.. code-block:: yaml
---
version: '1.0'
family:
type: leadership
leader:
multi: true
default:
- val1
- val2
follower1:
default:
type: jinja
jinja: 'the index is: {{ param1 }}'
params:
param1:
type: index
Calculation via a variable
-----------------------------
Copy a variable in another:
.. code-block:: yaml
---
version: '1.0'
my_variable:
multi: true
default:
- val1
- val2
my_calculated_variable:
multi: true
default:
type: variable
variable: rougail.my_variable
Copy one variable to another if the source has no `property` problem:
.. code-block:: yaml
---
version: '1.0'
my_variable:
default: val1
disabled: true
my_calculated_variable:
multi: true
default:
type: variable
variable: rougail.my_variable
propertyerror: false
Copy two non-multiple variables into a multiple variable:
.. code-block:: yaml
---
version: '1.0'
my_variable_1:
default: val1
my_variable_2:
default: val2
my_calculated_variable:
multi: true
default:
- type: variable
variable: rougail.my_variable_1
- type: variable
variable: rougail.my_variable_2
A variable in a dynamic family can also be used in a calculation.
For example using the variable for a particular suffix:
.. code-block:: yaml
---
version: '1.0'
varname:
multi: true
default:
- val1
- val2
my_dyn_family_:
type: dynamic
variable: rougail.varname
description: 'Describe '
my_dyn_var_:
type: string
default:
type: suffix
all_dyn_var:
default:
type: variable
variable: rougail.my_dyn_family_val1.my_dyn_var_val1
In this case, we recover the value `val1`.
Second example using the variable for all suffixes:
.. code-block:: yaml
---
version: '1.0'
varname:
multi: true
default:
- val1
- val2
my_dyn_family_:
type: dynamic
variable: rougail.varname
description: 'Describe '
my_dyn_var_:
type: string
default:
type: suffix
all_dyn_var:
multi: true
default:
type: variable
variable: rougail.my_dyn_family_.my_dyn_var_
In this case, we recover the `val1` and `val2` list.
Calculation via a suffix
---------------------------
.. code-block:: yaml
---
version: '1.0'
varname:
multi: true
default:
- val1
- val2
my_dyn_family_:
type: dynamic
variable: rougail.varname
description: 'Describe '
my_dyn_var_:
type: string
default:
type: suffix
Calculation via an index
--------------------------
.. code-block:: yaml
---
version: '1.0'
family:
type: leadership
leader:
multi: true
default:
- val1
- val2
follower1:
type: number
default:
type: index
Redefinition
----------------
In a first dictionary, let's declare our variable and our calculation:
.. code-block:: yaml
---
version: '1.0'
my_calculated_variable:
default:
type: jinja
jinja: 'the value is calculated'
In a second dictionary, it is possible to redefine the calculation:
.. code-block:: yaml
---
version: '1.0'
my_calculated_variable:
redefine: true
default:
type: jinja
jinja: 'the value is redefined'
In a third dictionary, we even can delete the calculation if needed:
.. code-block:: yaml
---
version: '1.0'
my_calculated_variable:
redefine: true
default: null

192
docs/gettingstarted.rst Normal file
View file

@ -0,0 +1,192 @@
.. |Tiramisu| replace:: Tiramisu
.. _tiramisu: https://forge.cloud.silique.fr/stove/tiramisu
Getting started
====================
What is a consistency handling system ?
------------------------------------------------
.. questions:: Question: "OK, I have understood that the Rougail stuff enables me to take advantage of |Tiramisu|. But what is all this for? What is exactly a consistency handling system? And again, what is this |Tiramisu| library used for?"
*Answer*: Well, let's explain what |Tiramisu| is and how we are using the |Tiramisu| library.
.. glossary::
Tiramisu
|Tiramisu| is a consistency handling system that was initially designed
in the configuration management scope. To put it more simply,
this library is generally used to handle configuration options.
It manages variables and group of variables. In the Tiramisu scope we call
it *options* and *option descriptions*.
In the Rougail scope, we call it :term:`variable`\ s and :term:`families`.
In Rougail, the families and variables are located in the :term:`dictionaries`.
And this is what we are going to explain in this page.
The dictionaries
---------------------
.. glossary::
dictionary
dictionaries
A dictionary in the Rougail meaning is a YAML file that describes variables
and their dependencies / consistencies.
There can be a lot of dictionary files located in many different folders.
Rougail reads all the dictionaries and loads them into a single object
that handles the variables consistency.
.. image:: images/schema.png
The main advantage is that declaring variables and writing consistency is a simple
as writing YAML. It is not necessary to write :term:`Tiramisu` code.
It simplifies a lot of things.
And rather than writing :term:`Tiramisu` code, we can declare variables and describe the relationships between variables in a declarative mode.
Once the dictionaries are loaded by Rougail, we find all the power of the :term:`Tiramisu` configuration management tool.
The dictionaries YAML format
---------------------------------
Before getting started with Rougail we need to learn the specifics of the YAML dictionaries file format (as well as some templating concepts).
.. FIXME parler de jinja https://jinja.palletsprojects.com
Here is a :term:`dictionary` example:
.. code-block:: yaml
:linenos:
---
version: '1.0'
proxy:
description: Configure Proxy Access to the Internet
type: family
Line 3, we declare a **variable** named `proxy` with his `description` line 4 and his `type` line 5.
The variables
-----------------
variable
Here is a second definition of a :term:`variable`: it is a declaration unit that represents a business domain metaphor,
the most common example is that a variable represents a configuration option
in a application, but a variable represents something more that a configuration option.
It provides a business domain specific representation unit.
.. note:: dictionaries can just define a list of variables, but we will see that
we can specify a lot more. We can define variables **and** their relations,
and the consistency between them.
In the next step, we will explain through a tutorial how to construct a list of variables.
Families of variables
--------------------------
.. glossary::
family
families
A family of variables is simply a collection of variables that refer to
the same business model category. It's just a variables container.
Think of it as a container as well as a namespace.
A "hello world" with Rougail
------------------------------
We're gonna make the simplest possible example.
.. prerequisites:: Prerequisites
We assume that Rougail's library is installed on your computer (or in a virtual environment).
.. exercise:: Let's make a Hello world
Here is the tree structure we want to have::
workplace
├── dict
   │   ├── hello.yml
   └── hello.py
- Let's make a :file:`workplace` directory, with a :file:`dict` subfolder.
- First, we need a :term:`dictionary`, so let's make the :file:`hello.yml` file
which is located in the :file:`dict` subfolder, with the following content:
.. code-block:: yaml
:caption: The `hello.yaml` file
---
version: '1.0'
hello:
default: world
- Then we make a :file:`hello.py` in our root :file:`workplace` directory:
.. code-block:: python
:caption: The :file:`hello.py` file
from rougail import Rougail, RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
rougail = Rougail()
config = rougail.get_config()
print(config.value.get())
.. demo:: Let's run the :file:`hello.py` script
We launch the script:
.. code-block:: bash
python hello.py
And we obtain the following result:
.. code-block:: python
{'rougail.hello': 'world'}
**Congratulations ! You have successfully completed your first Rougail script.**
A "Hello, <name> " sample with a family
------------------------------------------
Let's continuing on our "Hello world" theme and add a :term:`family` container.
.. code-block:: yaml
:caption: the :file:`hello.yml` file
:linenos:
---
version: '1.0'
world:
description: Hello world family container
name:
description: Somebody to say hello
default: rougail
Here, we have a family named `world`.
This family contains a variable named `name`
Again, let's validate this YAML file against Rougail's API:
.. code-block:: bash
python hello.py
We then have the output:
.. code-block:: python
{'rougail.world.name': 'rougail'}

View file

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

View file

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

BIN
docs/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View file

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

64
docs/index.rst Normal file
View file

@ -0,0 +1,64 @@
.. meta::
:description: Rougail python library home page
:keywords: python, Rougail, Tiramisu
:http-equiv=Pragma: no-cache
.. title:: Rougail
Rougail
===========
.. image:: images/logo.png
- is a `delicious cooked dish <https://fr.wikipedia.org/wiki/Rougail>`_ from the Mauritius and Reunion Islands,
- it is also a `Python3 <https://www.python.org/>`_ library which enables us to conveniently load application :term:`variable`\ s in a simple `YAML <https://yaml.org/>`_ format in such a way that the end user consumer can handle them consistently (that is, against an user-defined consistency).
In other words, using Rougail in your application or your python libraries can tansform end user consumer defined consistency rules into highly consistent business objects.
We then have to say that the handling system used to ensure the variables integrity is another python library, called :term:`Tiramisu`. Rougail is currently strongly affiliated with Tiramisu.
.. note:: Rougail is currently intended to work in coordination with :term:`Tiramisu` and **is not** intended to be connected with any other consistency handling system.
Explained differently, Rougail allows you to easily implement an integration of the powerful tiramisu consistency handling system.
.. toctree::
:titlesonly:
:caption: Getting started
gettingstarted
tutorial
.. toctree::
:titlesonly:
:caption: The library
library
configuration
.. toctree::
:titlesonly:
:caption: The dictionaries
dictionary
dict_convention
.. toctree::
:titlesonly:
:caption: The variables
variable
family
fill
Value checks <check>
condition
.. toctree::
:titlesonly:
:caption: Notes
developer
.. rubric:: Index page
- :ref:`genindex`

141
docs/library.rst Normal file
View file

@ -0,0 +1,141 @@
`Rougail`'s library description
=================================
Rougail is a configuration management library that allows you to load variables in a simple and convenient way.
In the following examples, we will use a specific configuration of Rougail. You will find all the options to :doc:`customize the directories structure used <configuration>`.
To load the configuration you must import the `RougailConfig` class and set the `dictionaries_dir` values:
.. code-block:: python
from rougail import RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
Let's convert a dictionary
-----------------------------
As a reminder, a :term:`dictionary` is a set of instructions that will allow us to create :term:`families` and :term:`variables`.
Let's start by creating a simple dictionary.
Here is a first :file:`dict/00-base.yml` dictionary:
.. code-block:: yaml
---
version: '1.0'
my_variable:
default: my_value
Then, let's create the :term:`Tiramisu` objects via the following script:
.. code-block:: python
:caption: the `script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
rougail = Rougail()
config = rougail.get_config()
print(config.value.get())
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
{'rougail.my_variable': 'my_value'}
Let's convert an extra dictionary
-------------------------------------
.. index:: extras
The default namespace for variables and families is `rougail`. It is possible to define other namespaces. These additional namespaces are called `extras`.
.. FIXME: faire une page pour les extras
Additional namespaces are defined during configuration.
For example, here's how to add an `example` namespace:
.. code-block:: python
RougailConfig['extra_dictionaries']['example'] = ['extras/']
Then let's create an extra :term:`dictionary` :file:`extras/00-base.yml`:
.. code-block:: yaml
:caption: the :file:`extras/00-base.yml` file content
---
version: '1.0'
my_variable_extra:
default: my_value_extra
Then, let's create the :term:`Tiramisu` objects via the following :file:`script.py` script:
.. code-block:: python
:caption: the :file:`script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['extra_dictionaries']['example'] = ['extras/']
rougail = Rougail()
config = rougail.get_config()
print(config.value.dict())
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
{'rougail.my_variable': 'my_value', 'example.my_variable_extra': 'my_value_extra'}
Let's create a custom function
----------------------------------
We create the complementary :term:`dictionary` named :file:`dict/01-function.yml` so that the `my_variable_jinja` variable is :term:`calculated`:
.. code-block:: yaml
---
version: '1.0'
my_variable_jinja:
type: "string"
default:
type: jinja
jinja: "{{ return_no() }}"
Then let's define the :func:`return_no` function in :file:`functions.py`:
.. code-block:: python
:caption: the :file:`functions.py` content
def return_no():
return 'no'
Then, let's create the :term:`Tiramisu` objects via the following script:
.. code-block:: python
:caption: the `script.py` file content
from rougail import Rougail, RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['extra_dictionaries']['example'] = ['extras/']
RougailConfig['functions_file'] = 'functions.py'
rougail = Rougail()
config = rougail.get_config()
print(config.value.dict())
Let's execute `script.py`:
.. code-block:: bash
$ python3 script.py
{'rougail.my_variable': 'my_value', 'rougail.my_variable_jinja': 'no', 'example.my_variable_extra': 'my_value_extra'}
The value of the `my_variable_extra` variable is calculated, and it's value comes from the :func:`return_no` function.

81
docs/requirements.txt Normal file
View file

@ -0,0 +1,81 @@
alabaster==0.7.13
asttokens==2.4.1
attrs==23.1.0
Babel==2.13.1
certifi==2023.7.22
charset-normalizer==3.3.2
click==8.1.7
colorama==0.4.6
comm==0.2.0
debugpy==1.8.0
decorator==5.1.1
docutils==0.18.1
exceptiongroup==1.1.3
executing==2.0.1
fastjsonschema==2.18.1
greenlet==3.0.1
idna==3.4
imagesize==1.4.1
importlib-metadata==6.8.0
ipykernel==6.26.0
ipython==8.17.2
jedi==0.19.1
Jinja2==3.1.2
jsonschema==4.19.2
jsonschema-specifications==2023.7.1
jupyter-cache==1.0.0
jupyter_client==8.6.0
jupyter_core==5.5.0
livereload==2.6.3
markdown-it-py==3.0.0
MarkupSafe==2.1.3
matplotlib-inline==0.1.6
mdit-py-plugins==0.4.0
mdurl==0.1.2
myst-nb==1.0.0
myst-parser==2.0.0
nbclient==0.9.0
nbformat==5.9.2
nest-asyncio==1.5.8
packaging==23.2
parso==0.8.3
pexpect==4.8.0
platformdirs==4.0.0
prompt-toolkit==3.0.40
psutil==5.9.6
ptyprocess==0.7.0
pure-eval==0.2.2
Pygments==2.16.1
python-dateutil==2.8.2
PyYAML==6.0.1
pyzmq==25.1.1
referencing==0.30.2
requests==2.31.0
rpds-py==0.12.0
six==1.16.0
snowballstemmer==2.2.0
Sphinx==7.2.6
sphinx-autobuild==2021.3.14
sphinx-copybutton==0.5.2
sphinx-lesson==0.8.15
sphinx-minipres==0.2.1
sphinx-rtd-theme==1.3.0
sphinx-rtd-theme-ext-color-contrast==0.3.1
sphinx-tabs==3.4.4
sphinx-togglebutton==0.3.2
sphinxcontrib-applehelp==1.0.7
sphinxcontrib-devhelp==1.0.5
sphinxcontrib-htmlhelp==2.0.4
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.6
sphinxcontrib-serializinghtml==1.1.9
SQLAlchemy==2.0.23
stack-data==0.6.3
tabulate==0.9.0
tornado==6.3.3
traitlets==5.13.0
typing_extensions==4.8.0
urllib3==2.0.7
wcwidth==0.2.9
zipp==3.17.0

829
docs/tutorial.rst Normal file
View file

@ -0,0 +1,829 @@
Tutorial: a real world sample
==============================
.. demo:: Demonstration : configuring (the setting of) your favorite web browser
This tutorial shows to you an example of Rougail use on
how to set a proxy in the `Mozilla Firefox <https://www.mozilla.org/en-US/firefox/new/>`_ browser.
More precisely, this tutorial aims at reproducing this Mozilla Firefox settings page:
.. image:: images/firefox.png
.. important:: Here we are in the configuration validation use case,
that is the values entered by the user have to be validated.
It's a common use case, but not the only one.
Let's explain this use case.
The Firefox proxy configuration
-------------------------------------------
The `proxy` family
-------------------
Let's create our first :term:`dictionary`.
.. prerequisites:: Let's create a folder named `dict` and a dictionary file inside
We will put our dictionary files in this folder.
Then let's put our first dictionary file in this folder, named :file:`00-proxy.yml`
.. code-block:: yaml
:caption: the :file:`00-proxy.yml` file
:linenos:
---
version: '1.0'
proxy:
description: Proxy configuration in order to have access to the internet
type: family
We can see that we have defined a :term:`family` here, and this family is *empty*
(that is, the family container contains no variable yet).
.. admonition:: If a family is empty
We need to specify the :term:`family` type (line 5) here because if we don't,
the Rougail's type engine will infer it by default as a :term:`variable`.
It's because we don't have set any :term:`variable` inside.
.. note:: The variables will be created in several files for educational purposes.
Obviously all the variables can be put in the same file.
The proxy's configuration type
----------------------------------
In the Firefox configuration, it is possible to define several configuration modes,
from no proxy at all (`no proxy`) to a kind of automatic configuration mode from a file (`set up proxy configuration from a file`).
We're gonna create a first variable in this family with "Proxy mode" as the description.
Let's create a second :file:`dict/01-proxy_mode.yml` file.
.. code-block:: yaml
:caption: the :file:`001-proxy_mode.yml` file
:linenos:
---
version: '1.0'
proxy:
proxy_mode:
description: Proxy mode
type: choice
choices:
- No proxy
- Auto-detect proxy settings for this network
- Use system proxy settings
- Manual proxy configuration
- Automatic proxy configuration URL
default: No proxy
The `proxy_mode` variable requires a value (that is, `None` is not an option).
It shall have a value, but what if the user *does not* specify any value?
There is line 13, a possibility of setting a default value, wich is `No proxy` as the default.
The `proxy_mode` setting is "choice" (`type: choice`) means that
there is a list of available values that can be selected.
We say that the `proxy_mode` variable is *constrained* (by choices).
Line 8 to 12, we have the list of the possible (authorized) values:
- No proxy
- Auto-detect proxy settings for this network
- Use system proxy settings
- Manual proxy configuration
- Automatic proxy configuration URL
Now let's test our first two dictionaries:
>>> from rougail import Rougail, RougailConfig
>>> from pprint import pprint
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'No proxy'}
The manual mode
------------------
.. questions:: OK then. What happens when you select the "Manual proxy configuration"?
A good configuration design is to place all the proxy's manual configuration in a :term:`family`.
Let's create the :file:`dict/02-proxy_manual.yml` dictionary:
.. code-block:: yaml
:caption: the the :file:`dict/02-proxy_manual.yml` file
---
version: '1.0'
proxy:
manual:
description: Manual proxy configuration
type: family
disabled:
type: jinja
jinja: |
{% if rougail.proxy.proxy_mode != 'Manual proxy configuration' %}
the proxy mode is not manual
{% endif %}
Well, if the user selects the "Manual proxy configuration" proxy mode, we want to see a new subfamily (that is, a new set of configuration variables) called `manual` to appear (which is disabled).
.. glossary::
subfamily
A subfamily is just a family inside a family, a family that contains a family.
.. questions:: What about this `Jinja` type?
If the :term:`Jinja` template returns some text, then the family will be `disabled`. Otherwise it is accessible.
Deactivating a family means that we will not be able to access it as well as the variables or families included in this family.
.. note:: If the Jinja template does not return any text, the variable will be **enabled**.
Here we are using the Jinja condition statement.
.. glossary::
Jinja
`Jinja <https://jinja.palletsprojects.com>`_ is a template engine.
we are using Jinja in a classical way, that is, Jinja allows us to handle different cases,
for example with the `if` statement.
The HTTP proxy configuration
------------------------------
In this family let's add a *subfamily* named `http_proxy`, containing the address and port configuration variables.
Let's create the :file:`dict/03-proxy_manual_http_proxy.yml` dictionary:
.. code-block:: yaml
:caption: the the :file:`dict/02-proxy_manual.yml` file
:linenos:
---
version: '1.0'
proxy:
manual:
http_proxy:
description: HTTP Proxy
address:
description: HTTP address
type: domainname
port:
description: HTTP Port
type: port
default: '8080'
Both variables `address` and `port` have particular types (respectively `domainname` line 9 and `port` line 12) to validate the values configured by the user.
.. note:: No need to specify the type of the `http_proxy` as a family type, because here we have declared variables inside of it.
Duplicating the HTTP configuration to HTTPS
---------------------------------------------
We then want to offer the user the possibility of providing the same proxy for the HTTPS requests. Let's create the :file:`dict/04-proxy_manual_http_use_for_https.yml` file:
.. code-block:: yaml
:caption: the :file:`dict/04-proxy_manual_http_use_for_https.yml` file
version: '1.0'
proxy:
manual:
use_for_https:
description: Also use this proxy for HTTPS
type: boolean
This variable is a `boolean` type, its default value is `True`.
HTTPS proxy configuration detail
-----------------------------------
Let's add a new subfamily named `ssl_proxy`, containing the `address` and `port` variables.
Let's create the :file:`dict/05-proxy_manual_ssl_proxy.yml` file:
.. code-block:: yaml
:caption: the :file:`dict/04-proxy_manual_http_use_for_https.yml` file
:linenos:
---
version: '1.0'
proxy:
manual:
ssl_proxy:
description: HTTPS Proxy
hidden:
type: variable
variable: rougail.proxy.manual.use_for_https
address:
description: HTTPS address
type: domainname
default:
type: jinja
jinja: |
{% if rougail.proxy.manual.use_for_https %}
{{ rougail.proxy.manual.http_proxy.address }}
{% endif %}
port:
description: HTTPS Port
type: port
default:
type: jinja
jinja: |
{% if rougail.proxy.manual.use_for_https %}
{{ rougail.proxy.manual.http_proxy.port }}
{% endif %}
Depending on the value of the `rougail.proxy.mandatory.use_for_https` variable, this family will appear or disappear (the `hidden` setting line 7). Unlike earlier, this time it is not necessary to use a Jinja function.
Let's notice that the family is not disabled because the variables will need to remain accessible (yet in `read-only` mode).
The address and port variables are copied from HTTP to HTTPS if `rougail.proxy.use_for_https` is set to `True`.
Now let's test all of it:
>>> from rougail import Rougail, RougailConfig
>>> from pprint import pprint
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'No proxy'}
At this time the proxy is not configured yet, so we do not see any variables.
Let's look at what happens if we try to access the `rougail.proxy.manual` variable if we are not in manual mode:
.. code-block:: python
>>> pprint(config.option('rougail.proxy.manual').value.get(), sort_dicts=False)
We have an error (with the message defined in the Jinja template):
.. code-block:: python
tiramisu.error.PropertiesOptionError: cannot access to optiondescription "Manual proxy configuration" because has property "disabled" (the mode proxy is not manual)
Let's configure the proxy in manual mode
>>> config.property.read_write()
>>> config.option('rougail.proxy.proxy_mode').value.set('Manual proxy configuration')
>>> config.option('rougail.proxy.manual.http_proxy.address').value.set('proxy.example')
>>> pprint(config.value.get(), sort_dicts=False)
We can see that the returned variables does have the desired values:
.. code-block:: python
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
'rougail.proxy.manual.http_proxy.address': 'proxy.example',
'rougail.proxy.manual.http_proxy.port': '8080',
'rougail.proxy.manual.use_for_https': True}
Let's set the `read_only` mode:
.. code-block:: python
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
'rougail.proxy.manual.http_proxy.address': 'proxy.example',
'rougail.proxy.manual.http_proxy.port': '8080',
'rougail.proxy.manual.use_for_https': True,
'rougail.proxy.manual.ssl_proxy.address': 'proxy.example',
'rougail.proxy.manual.ssl_proxy.port': '8080'}
In the `read_only` mode, we can see that the HTTPS configuration appears.
.. note:: We can see that `rougail.proxy.manual.http_proxy` values have been copied
in `rougail.proxy.manual.ssl_proxy` too...
Changing values programmatically
--------------------------------------
We are going to use the :term:`Tiramisu` API to manipulate programmatically the different variables.
First, let's set `rougail.proxy.manual.use_for_https` to `False`. It is now possible
to configure the HTTPS:
.. code-block:: python
>>> config.property.read_write()
>>> config.option('rougail.proxy.manual.use_for_https').value.set(False)
>>> config.option('rougail.proxy.manual.ssl_proxy.address').value.set('other.proxy.example')
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
'rougail.proxy.manual.http_proxy.address': 'proxy.example',
'rougail.proxy.manual.http_proxy.port': '8080',
'rougail.proxy.manual.use_for_https': False,
'rougail.proxy.manual.ssl_proxy.address': 'other.proxy.example',
'rougail.proxy.manual.ssl_proxy.port': '8080'}
The value of the variable `rougail.proxy.manual.ssl_proxy.address` has actually been modified.
But if this variable is hidden again, then the value comes back to the default value:
.. code-block:: python
>>> config.option('rougail.proxy.manual.use_for_https').value.set(False)
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
'rougail.proxy.manual.http_proxy.address': 'proxy.example',
'rougail.proxy.manual.http_proxy.port': '8080',
'rougail.proxy.manual.use_for_https': False,
'rougail.proxy.manual.ssl_proxy.address': 'proxy.example',
'rougail.proxy.manual.ssl_proxy.port': '8080'}
SOCK's proxy configuration
-------------------------------
Let's add a new :term:`subfamily` named `socks_proxy` with the `address`,
`port` and `version` variables.
Let's create the :file:`dict/06-proxy_manual_socks_proxy.yml` file:
.. code-block:: yaml
:caption: the :file:`dict/06-proxy_manual_socks_proxy.yml` file
---
version: '1.0'
proxy:
manual:
socks_proxy:
description: SOCKS Proxy
address:
description: SOCKS Address
type: domainname
port:
description: SOCKS Port
type: port
version:
description: SOCKS host version used by proxy
type: choice
choices:
- v4
- v5
default: v5
There's nothing new to learn with this file.
The automatic detection mode
------------------------------
Let's add a new variable named `auto`.
Let's create the :file:`dict/07-proxy_auto.yml` file:
.. code-block:: yaml
:caption: the :file:`dict/07-proxy_auto.yml` file
---
version: '1.0'
proxy:
auto:
type: web_address
description: Automatic proxy configuration URL
disabled:
type: jinja
jinja: |
{% if rougail.proxy.proxy_mode != 'Automatic proxy configuration URL' %}
the proxy mode is not automatic
{% endif %}
The `web_address` type imposes a value starting with `http://` or `https://`.
This variable is activated when the proxy is in automatic mode.
The proxy's exceptions
---------------------------
Finally, let's add a variable containing proxy exceptions.
Let's create the :file:`dict/07-proxy_no_proxy.yml` file:
.. code-block:: yaml
:caption: the :file:`dict/07-proxy_no_proxy.yml` file
:linenos:
---
version: '1.0'
proxy:
no_proxy:
description: Address for which proxy will be desactivated
multi: true
type: "domainname"
params:
allow_ip: true
allow_cidr_network: true
allow_without_dot: true
allow_startswith_dot: true
disabled:
type: jinja
jinja: |
{% if rougail.proxy.proxy_mode == 'No proxy' %}
proxy mode is no proxy
{% endif %}
mandatory: false
This `no_proxy` variable is much like a `domainname` type except that we add
a `params` line 7, we authorize the :
- IP
- CIDR networks
- machine names (without `'.'`)
- sub-domaines like `.example`
There can be multiple exceptions to the proxy, so the variable is :term:`multi` (line5).
This variable is only accessible if no proxy is defined (`disabled`).
.. glossary::
multi
A multi is a multiple variable, that is a variable that can have multiple values.
The `no_proxy` variable do not requires a value (that is, `None` is an option),
there is line 19 this statement `mandatory: false` which means that this variable is not mandatory.
Let's test it:
>>> from rougail import Rougail, RougailConfig
>>> from pprint import pprint
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.property.read_write()
>>> config.option('rougail.proxy.proxy_mode').value.set('Manual proxy configuration')
>>> config.option('rougail.proxy.manual.http_proxy.address').value.set('proxy.example')
>>> config.option('rougail.proxy.no_proxy').value.set(['.example', '192.168.1.1'])
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
It outputs:
.. code-block:: python
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
'rougail.proxy.manual.http_proxy.address': 'proxy.example',
'rougail.proxy.manual.http_proxy.port': '8080',
'rougail.proxy.manual.use_for_https': True,
'rougail.proxy.manual.ssl_proxy.address': 'proxy.example',
'rougail.proxy.manual.ssl_proxy.port': '8080',
'rougail.proxy.manual.socks_proxy.address': None,
'rougail.proxy.manual.socks_proxy.port': None,
'rougail.proxy.manual.socks_proxy.version': 'v5',
'rougail.proxy.no_proxy': ['.example', '192.168.1.1']}
But not possible to put an invalid value:
.. code-block:: python
>>> config.option('rougail.proxy.no_proxy').value.set(['.example', '192.168.1.1', 'not valid'])
[..]
tiramisu.error.ValueOptionError: "not valid" is an invalid domain name for "Address for which proxy will be desactivated", could be a IP, otherwise must start with lowercase characters followed by lowercase characters, number, "-" and "." characters are allowed
The authentification request
--------------------------------
Nothing special when creating the authentication request. To do this, let's create a `dict/08-proxy_prompt_authentication.yml` file:
.. code-block:: yaml
:caption: the :file:`dict/08-proxy_prompt_authentication.yml` file
:linenos:
---
version: '1.0'
proxy:
prompt_authentication:
description: Prompt for authentication if password is saved
type: boolean
default: true
disabled:
type: jinja
jinja: |
{% if rougail.proxy.proxy_mode == 'No proxy' %}
proxy mode is no proxy
{% endif %}
The proxy SOCKS v5's DNS
------------------------------
The DNS variable for the SOCKS v5 proxy only appears if the proxy is configured and the version of the SOCKS proxy selected is `v5`.
Let's create a `dict/09-proxy_proxy_dns_socks5.yml` file:
.. code-block:: yaml
:caption: the :file:`dict/09-proxy_proxy_dns_socks5.yml` file
:linenos:
---
version: '1.0'
proxy:
proxy_dns_socks5:
description: Use proxy DNS when using SOCKS v5
type: boolean
default: false
disabled:
type: jinja
params:
socks_version:
type: variable
variable: rougail.proxy.manual.socks_proxy.version
propertyerror: false
jinja: |
{% if rougail.proxy.proxy_mode == 'No proxy' %}
the proxy mode is no proxy
{% elif socks_version is undefined or socks_version == 'v4' %}
socks version is v4
{% endif %}
The difficulty here is that the `rougail.proxy.manual.socks_proxy.version` variable
can be deactivated (and therefore not usable in a calculation).
.. FIXME definir ce qu'est une calculation
In this case, we will add a parameter (here called `socks_version`) which will contain,
if there is no property error, the value of the variable.
Otherwise the parameter will not be passed to the Jinja template.
This is why it is necessary to test in the Jinja template whether the `socks_version` variable really exists.
The DNS over HTTPS
----------------------
Finally we will configure DNS over HTTPS in the 10-proxy_dns_over_https.yml file:
Let's create a `dict/10-proxy_dns_over_https.yml` file:
.. code-block:: yaml
:caption: the :file:`dict/10-proxy_dns_over_https.yml` file
:linenos:
---
version: '1.0'
proxy:
dns_over_https:
description: DNS over HTTPS
enable_dns_over_https:
description: Enable DNS over HTTPS
type: boolean
default: false
provider:
description: Use Provider
type: choice
choices:
- Cloudflare
- NextDNS
- Custom
default: Cloudflare
disabled:
type: jinja
jinja: |
{% if not rougail.proxy.dns_over_https.enable_dns_over_https %}
Enable DNS over HTTPS is False
{% endif %}
custom_dns_url:
description: Custom DNS URL
type: web_address
disabled:
type: jinja
params:
provider:
type: variable
variable: rougail.proxy.dns_over_https.provider
propertyerror: false
jinja: |
{% if provider is not defined or provider != 'Custom' %}
provider is not custom
{% endif %}
validators:
- type: jinja
jinja: |
{% if rougail.proxy.dns_over_https.custom_dns_url.startswith('http://') %}
only https is allowed
{% endif %}
.. FIXME : define validators
The only particularity here is that we added additional validation (validators) to the `custom_dns_url` variable. Only an address starting with `https://` is allowed (not `http://`).
----
The FoxyProxy type's proxy configuration
--------------------------------------------
Here is now the integration of part of the Firefox FoxyProxy plugin.
The idea is to have a namespace specific to FoxyProxy and to find in it part of the settings that we will have made in the main namespace.
This is what the page looks like:
.. image:: images/foxyproxy.png
It is possible, in this plugin, to specify an unlimited number of proxies.
Our `proxy` family will no longer be of the `family` type as before but of another type : the :term:`leadership` type.
.. FIXME: expliquer ce qu'est le type leardership
Here is the complete content of the FoxyProxy type proxy configuration
(to be put in the `foxyproxy/00-base.yml` file):
.. code-block:: yaml
:caption: the :file:``foxyproxy/00-base.yml`` file
:linenos:
---
version: '1.0'
proxy:
_type: leadership
title:
description: Title or Description
multi: true
color:
description: Color
type:
type: choice
choices:
- HTTP
- HTTPS/SSL
- SOCKS5
- SOCKS4
- PAC URL
- WPAD
- System (use system settings)
- Direct (no proxy)
default: Direct (no proxy)
address:
description: IP address, DNS name, server name
multi: true
disabled:
type: jinja
jinja: |
{% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %}
proxy does not need address
{% endif %}
default:
type: jinja
params:
firefox_address:
type: variable
variable: rougail.proxy.manual.http_proxy.address
propertyerror: false
jinja: |
{% if firefox_address is not undefined %}
{{ firefox_address }}
{% endif %}
port:
description: Port
type: port
default:
type: jinja
params:
firefox_port:
type: variable
variable: rougail.proxy.manual.http_proxy.port
propertyerror: false
jinja: |
{% if firefox_port is not undefined %}
{{ firefox_port }}
{% endif %}
disabled:
type: jinja
jinja: |
{% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %}
proxy does not need port
{% endif %}
username:
description: Username
type: unix_user
mandatory:
type: jinja
jinja: |
{% if foxyproxy.proxy.password %}
username is mandatory
{% endif %}
disabled:
type: jinja
jinja: |
{% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %}
proxy does not need username
{% endif %}
password:
description: Password
type: secret
disabled:
type: jinja
jinja: |
{% if foxyproxy.proxy.type not in ['HTTP', 'HTTPS/SSL', 'SOCKS5', 'SOCKS4'] %}
proxy does not need password
{% endif %}
url:
type: web_address
disabled:
type: jinja
jinja: |
{% if foxyproxy.proxy.type not in ['PAC URL', 'WPAD'] %}
proxy does not need url
{% endif %}
A few comments:
- in the `foxyproxy.proxy` :term:`leader` family there is a variable named `type` (line 4), this may conflict with the `type` attribute (specified line 10). In this case, to specify the type we use the `_type` attribute
- a :term:`follower` variable can also be multiple
(which is the case for `foxyproxy.proxy.address`)
- `foxyproxy.proxy.username` (line 62) becomes :term:`mandatory` if `foxyproxy.proxy.password`
is specified, in fact a password without a username is meaningless
Let's test it:
>>> from rougail import Rougail, RougailConfig
>>> from pprint import pprint
>>> RougailConfig['dictionaries_dir'] = ['dict']
>>> RougailConfig['extra_dictionaries']['foxyproxy'] = ['foxyproxy/']
>>> rougail = Rougail()
>>> config = rougail.get_config()
>>> config.option('rougail.proxy.proxy_mode').value.set('Manual proxy configuration')
>>> config.option('rougail.proxy.manual.http_proxy.address').value.set('proxy.example')
>>> config.option('foxyproxy.proxy.title').value.set(['MyProxy'])
>>> config.option('foxyproxy.proxy.type', 0).value.set('HTTP')
>>> config.option('foxyproxy.proxy.color', 0).value.set('#00000')
>>> config.property.read_only()
>>> pprint(config.value.get(), sort_dicts=False)
The output is:
.. code-block:: python
{'rougail.proxy.proxy_mode': 'Manual proxy configuration',
'rougail.proxy.manual.http_proxy.address': 'proxy.example',
'rougail.proxy.manual.http_proxy.port': '8080',
'rougail.proxy.manual.use_for_https': True,
'rougail.proxy.manual.ssl_proxy.address': 'proxy.example',
'rougail.proxy.manual.ssl_proxy.port': '8080',
'rougail.proxy.manual.socks_proxy.address': None,
'rougail.proxy.manual.socks_proxy.port': None,
'rougail.proxy.manual.socks_proxy.version': 'v5',
'rougail.proxy.no_proxy': [],
'rougail.proxy.proxy_dns_socks5': False,
'rougail.proxy.dns_over_https.enable_dns_over_https': False,
'foxyproxy.proxy.title': [{'foxyproxy.proxy.title': 'MyProxy',
'foxyproxy.proxy.color': '#00000',
'foxyproxy.proxy.type': 'HTTP',
'foxyproxy.proxy.address': ['proxy.example'],
'foxyproxy.proxy.port': '8080',
'foxyproxy.proxy.username': None,
'foxyproxy.proxy.password': None}]}
The choice we made here is to make `foxyproxy.proxy.username` :term:`mandatory` if a password is specified in the `foxyproxy.proxy.password` variable.
It makes sense to have a username without a password (in this case the password will be requested when connecting to the proxy). But the opposite does not make sense.
From a user point of view this may seem disturbing (if you enter the password, you have to return to the previous option to specify the password).
It is possible to reverse the logic. If the `foxyproxy.proxy.username` variable is set, the `foxyproxy.proxy.password` variable becomes editable.
None of this two variables needs to be :term:`mandatory`.
If you prefer this option, here is a second extra dictionary :file:`foxyproxy/01-redefine.yml` which will redefine the behavior only of the `foxyproxy.proxy.username` and `foxyproxy.proxy.password` variables:
.. code-block:: yaml
:caption: the :file:`foxyproxy/01-redefine.yml` file
:linenos:
---
version: '1.0'
proxy:
username:
redefine: true
# suppress mandatory constrainte
mandatory: false
password:
redefine: true
hidden:
type: jinja
jinja: |
{% if not foxyproxy.proxy.username %}
no username defined
{% endif %}
**It's up to you to play now !**

284
docs/variable.rst Normal file
View file

@ -0,0 +1,284 @@
The variables
===================
Synopsis
------------
.. glossary::
variable
variables
A variable is an abstract black box (container) paired with an associated symbolic name, which contains some defined or undefined quantity of data referred to as a `value`.
.. discussion:: This definition, makes a heavy use of data typing.
Indeed, depending on the type system definition of the constistency handling system used, variables may only be able to store a specified data type.
OK, variables are the containers for storing the values. It has something to do with typing.
But this is not just about typing.
Name
-------------
Variable's associated symbolic name.
It's best to follow the :ref:`convention on variable names`.
Parameters
-------------
.. list-table::
:widths: 15 45
:header-rows: 1
* - Parameter
- Comments
* - **help**
`string`
- Additional help associated with the variable.
* - **default**
- Default value(s) of the variable.
This value is typed, you must correctly fill out the YAML file to avoid defining a value with an incorrect type. For example, a `number` must be a digit type, a multiple variable must be a `list` type, ...
For a non :term:`leading` multiple variable, the first value defined in the list will also be the default value proposed if a new value is added to this variable.
* - **validators**
`list`
- Value validators.
Jinja template list. The value of the variable will be considered invalid if the template has a return value.
* - **auto_save**
`boolean`
- Variable with automatically modified value.
A variable with automatically modified value is a variable whose value will be considered as *modified* (that is, it is no longer the variable's default value).
For example, if the value of this variable comes from a calculation, the value will no longer be recalculated.
These variables are usually :term:`required` variables. In fact, these variables are only automatically modified if they have a value.
A :term:`leader` or :term:`follower` variable cannot have the `auto_save` property.
**Default value**: `false`
* - **mode**
`string`
- Variable's mode.
**Default value**: The `default` mode of a variable is the mode of the parent family.
Special cases :
- a variable with an automatically modified value or an automatic read-only variable is by default in `basic` mode
- if the variable is not in a family, the variable will have a `standard` mode by default
- a :term:`mandatory` variable without default value (calculate or not) will have a `basic` mode
* - **multi**
`boolean`
- The value of the variable is a list.
**Default value**: `false`
* - **unique**
`boolean`
- The :term:`multiple` type variable accepts the same value several times. If unique is set to `false`, a :term:`multiple` variable only accepts the same value once in the list.
**Default value**: `false`
* - **hidden**
`boolean` or :term:`calculation`
- Invisible variable.
Enables us to *hide* a variable.
This means that the variable will no longer be visible in `read-write` mode, but only for calculations or in `read-only` mode.
When a variable is made invisible, the user will not be able to modify its value; if he has already succeeded in modifying it, this value will not be taken into account.
**Default value**: `false`
* - **disabled**
`boolean` or :term:`calculation`
- Disabled variable.
Allows us to deactivate a variable.
This means that the variable will no longer be visible to the user but also to a :term:`calculation`.
**Default value**: `false`.
* - **mandatory**
`boolean` or :term:`calculation`
- Mandatory variable.
Variable whose value is `required`.
For a multiple variable, this means that the list shall not be empty.
**Default value**: `true`
* - **redefine**
`boolean`
- It is possible to define a variable in one :term:`dictionary` and change its behavior in a second :term:`dictionary`. In this case you must explicitly redefine the variable.
**Default value**: `false`
* - **exists**
`boolean`
- This attribute does two things:
- creates a variable if it does not exist in another :term:`dictionary` (otherwise do nothing), in this case the value of the attribute must be `true`
- in conjunction with the `redefine` attribute set to `true`, only modifies the behavior if it is pre-existing, in which case the attribute's value must be `false`.
**Default value**: `null`
* - **test**
`list`
- The `test` attribute is a special attribute that allows :term:`dictionary` designers to influence a test robot by specifying useful values to test.
Concretely, the content of this attribute is recorded in the `information` attribute of the corresponding `Tiramisu` option object.
Variables types
----------------
A variable **has a type**.
This type enables the variable to define the values that are accepted by this variable.
.. list-table::
:widths: 15 25 20 15
:header-rows: 1
* - Value
- Comments
- Parameters
- Samples
* - string
- character string (default type)
-
- test
"1"
"true"
* - number
- a number
- `min_number`: minimum number allowed
`max_number`: maximum number allowed
- 1
* - float
- a floating number
-
- 1.2
* - boolean
- A boolean, if no value is defined the default value of this variable will be `true`, the variable will also be :term:`mandatory` by default
-
- `true`
`false`
* - secret
- a secret (like a password, a private key, etc.)
-
- `hO_'hi`
* - mail
- a mail address
-
- test@rougail.example
* - unix_filename
- a file name in the Unix meaning
-
- :file:`/etc/passwd`
* - date
- a date in the format `%Y-%m-%d`
-
- `2021-01-30`
* - unix_user
- a user in the Unix meaning
-
- test
* - ip
- any kind of IPv4 address
- `private_only`: only private IPs (`false` by default)
`allow_reserved`: allows reserved IPs (`true` by default)
- `1.2.3.4`
* - cidr
- any IPv4 address in the CIDR format
- `private_only`: only private IPs (`false` by default)
`allow_reserved`: allows reserved IPs (`false` by default)
- `1.2.3.4/24`
* - netmask
- mask of an IPv4 address
-
- `255.255.255.0`
* - network
- network address
-
- `192.168.1.0`
* - network_cidr
- network address in CIDR format
-
- `192.168.1.0/24`
* - broadcast
- broadcast address
-
- `255.255.255.255`
* - netbios
- netbios name
-
- machine
* - domainname
- domain name
- `allow_ip`: allows an IP rather than a domain name (`false` by default)
`allow_cidr_network`: allows a CIDR type network address (`false` by default)
`allow_without_dot`: allows names without a dot (`false` by default)
`allow_startswith_dot`: allows starting with a point (`false` by default)
- `rougail.example`
* - hostname
- host name
- `allow_ip`: allows an IP rather than a domain name (`false` by default)
- machine
* - web_address
- web address
- `allow_ip`: allows an IP rather than a domain name (`false` by default)
`allow_without_dot`: allows names without a dot (`true` by default)
- http://rougail.example
* - port
- port
- `allow_range`: allows a port range, for example 80:85 (`false` by default)
`allow_zero`: allows port 0 (false by default)
`allow_wellknown`: allows ports from 1 to 1023 (`true` by default)
`allow_registred`: allows ports from 1024 to 49151 (`true` by default)
`allow_private`: allows ports greater than 49152 (`true` by default)
`allow_protocol`: allows the addition of the protocol, for example tcp:80 (`false` by default)
- 8080
* - mac
- MAC address
-
- 11:11:11:11:11:11
* - unix_permissions
- access rights to the file, directory, etc.
-
- 644
* - choice
- choice variable
-
-