feat: new format 1.0

This commit is contained in:
egarette@silique.fr 2023-10-12 08:17:30 +02:00
parent 940b20f5d9
commit 3e2176f737
4828 changed files with 64990 additions and 44312 deletions

127
README.md
View file

@ -6,136 +6,99 @@
Rougail is a free full-featured configuration manager library written in python3.
The configuration is describe in YAML ou XML dictionary files.
The configuration is describe in YAML dictionary files.
Those dictionaries are converted into [Tiramisu](https://framagit.org/tiramisu/tiramisu) objects and can generates configuration files with template written in [Cheetah](https://cheetahtemplate.org/) or [Jinja](https://jinja.palletsprojects.com/).
Those dictionaries are converted into [Tiramisu](https://framagit.org/tiramisu/tiramisu) objects.
Rougail can be incorporated with other technologies and stacks regardless of whether theyre written in Python or not.
## Simple example
Create directories:
Create a directory:
```bash
# mkdir dict tmpl tmp dest
# mkdir dict
```
### Dictionary
## Dictionary
A dictionary is a services and a variables description file.
A dictionary is a variables description file.
Create the file `dict/dictionary.yml`:
```yml
version: '0.10'
# describe a first service with a single file
services:
- service:
- name: my_service
file:
- engine: jinja
text: /etc/filename
---
version: '1.0'
# describe a variable my_first_variable
# and a family with a variable my_second_variable
variables:
- variable:
- name: my_first_variable
value:
- text: my_value
- family:
- name: my_family
variables:
- variable:
- name: my_second_variable
type: number
mandatory: true
value:
- text: 1
my_first_variable:
default: my_value
my_family:
my_second_variable:
type: number
mandatory: true
value: 1
```
### Template
## Generate variable
Create a [Jinja](https://jinja.palletsprojects.com/) template `tmpl/filename`:
```
My first value: {{ my_first_variable }}
My second value: {{ my_second_variable }}
```
### Generate template
#### With default value:
### With default value:
Here is a python3 example file:
```python
from rougail import Rougail, RougailConfig
from asyncio import run
from pprint import pprint
async def main():
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['templates_dir'] = ['tmpl']
RougailConfig['tmp_dir'] = 'tmp'
RougailConfig['destinations_dir'] = 'dest'
rougail = Rougail()
await rougail.template()
run(main())
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['templates_dir'] = ['tmpl']
RougailConfig['tmp_dir'] = 'tmp'
RougailConfig['destinations_dir'] = 'dest'
rougail = Rougail()
config = rougail.get_config()
pprint(config.value.get(), sort_dicts=False)
```
The destination file is generated:
The result is:
```bash
# cat dest/etc/filename
My first value: my_value
My second value: 1
```json
{'rougail.my_first_variable': 'my_value',
'rougail.my_family.my_second_variable': 1}
```
#### With modified value
### With modified value
Remove old generated file:
```bash
# rm -f dest/etc/filename
```
Use [Tiramisu](https://framagit.org/tiramisu/tiramisu) API to change values:
```python
from rougail import Rougail, RougailConfig
from asyncio import run
from pprint import pprint
async def main():
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['templates_dir'] = ['tmpl']
RougailConfig['tmp_dir'] = 'tmp'
RougailConfig['destinations_dir'] = 'dest'
rougail = Rougail()
config = await rougail.get_config()
await config.option('rougail.my_first_variable').value.set('modified_value')
await config.option('rougail.my_family.my_second_variable').value.set(2)
await rougail.template()
run(main())
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['templates_dir'] = ['tmpl']
RougailConfig['tmp_dir'] = 'tmp'
RougailConfig['destinations_dir'] = 'dest'
rougail = Rougail()
config = rougail.get_config()
config.option('rougail.my_first_variable').value.set('modified_value')
config.option('rougail.my_family.my_second_variable').value.set(2)
pprint(config.value.get(), sort_dicts=False)
```
The destination file is generated with new values:
```bash
# cat dest/etc/filename
My first value: modified_value
My second value: 2
```json
{'rougail.my_first_variable': 'modified_value',
'rougail.my_family.my_second_variable': 2}
```
# Link
* [Documentation (french)](doc/README.md)
* [Documentation](doc/README.md)
* [Licence ](LICENSE)
# Related projects
* [Tiramisu](https://framagit.org/tiramisu/tiramisu)
* [Tiramisu](https://forge.cloud.silique.fr/gnunux/tiramisu)
* [Risotto](https://cloud.silique.fr/gitea/risotto/risotto)

View file

@ -2,10 +2,13 @@
# Rougail
Rougail est un bibliothèque python3 qui permet de charger des dictionnaires (fichiers au format XML ou YAML), de charger les variables dans Tiramisu et de générer des templates Cheetah ou Jinja.
Rougail est un 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)
@ -14,28 +17,12 @@ Rougail est un bibliothèque python3 qui permet de charger des dictionnaires (fi
## Les dictionnaires
- [Les dictionnaires](dictionary/rougail.md)
- [Les dictionnaires extra](dictionary/extra.md)
- [Convention d'écriture d'un dictionnaire](dictionary/convention.md)
### Les variables
- [Les familles](family/README.md)
- [Les variables](variable/README.md)
### Les services
- [La gestion d'un fichier](service/file.md)
- [La gestion d'un certificat](service/certificate.md)
- [La gestion d'un fichier de service systemd](service/override.md)
- [La gestion d'une ip](service/ip.md)
### Les contraintes
- [Les calculs automatiques](fill/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 conditions](condition/README.md)
## Les templates
- [Les moteurs de templates](template/README.md)
- [Les patches](template/patch.md)
- [Les propriétés calculées](condition/README.md)

View file

@ -1,4 +1,208 @@
# Les vérifications des valeurs
# Fonction de vérification
- [Fonction de vérification](function.md)
- [Réfinition](redefine.md)
## 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 :
##FIXME##
```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
```
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,77 +0,0 @@
# Fonction de vérification
## Vérification stricte des valeurs
Une fonction de vérification est une fonction complémentaire au type qui permet de valider plus précisement le contenu d'une variable.
Voici un exemple simple de validation des valeurs :
```xml
<variables>
<variable name="my_variable"/>
</variables>
<constraints>
<check name="islower">
<target>my_variable</target>
</check>
</constraints>
```
En YAML :
```yml
variables:
- variable:
name: my_variable
constraints:
- check:
- name: islower
target:
- text: my_variable
```
La [cible (de type variable)](../target/variable.md) de la fonction de vérification est ici "my_variable".
Dans cette exemple, la valeur de la variable "my_variable" va être validé par la fonction islower.
Voici le contenu de la fonction :
```python
def islower(value):
if value is None:
return
if not value.islower():
raise ValueError(f'"{value}" is not lowercase string')
```
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 faire un raise de type ValueError avec, si possible, un message explicite.
À partir de maintenant seule None et des valeurs en minuscule seront autorisés.
Il est possible de définir des [paramètres](../param/README.md) à cette fonction.
## Vérification des valeurs avec avertissement
Dans la contrainte, il est possible de spécifier le niveau d'erreur et le mettre en avertissement :
```xml
<check name="islower" level="warning">
<target>my_variable</target>
</check>
```
En YAML :
```yml
- check:
- name: islower
level: warning
target:
- text: my_variable
```
Dans ce cas une valeur avec une majuscule sera accepté, mais un message d'avertissement apparaitra.

View file

@ -1,108 +0,0 @@
# Rédéfinition
## Redéfinition des vérification
Dans un premier dictionnaire déclarons notre variable et sa fonction de vérification :
```xml
<variables>
<variable name="my_variable"/>
</variables>
<constraints>
<check name="islower">
<target>my_variable</target>
</check>
</constraints>
```
En YAML :
```yml
variables:
- variable:
name: my_variable
constraints:
- check:
- name: islower
target:
- text: my_variable
```
Dans un second dictionnaire il est possible de redéfinir le calcul :
```xml
<variables>
<variable name="my_variable" redefine="True"/>
</variables>
<constraints>
<check name="isspace">
<target>my_variable</target>
</check>
</constraints>
```
En YAML :
```yml
variables:
- variable:
name: my_variable
redefine: true
constraints:
- check:
- name: isspace
target:
- text: my_variable
```
Dans ce cas, la fonction "islower" exécuté. Si cette fonction ne retourne pas d'erreur, la seconde fonction "isspace" sera exécuté.
## Redéfinition avec suppression d'un calcul
Il se peut que dans un dictionnaire on décide de vérifier la valeur d'une variable.
Dans un second dictionnaire il est possible de supprimer cette vérification.
Dans un premier dictionnaire déclarons notre variable et notre fonction de vérification :
```xml
<variables>
<variable name="my_variable"/>
</variables>
<constraints>
<check name="islower">
<target>my_variable</target>
</check>
</constraints>
```
En YAML :
```yml
variables:
- variable:
name: my_variable
constraints:
- check:
- name: islower
target:
- text: my_variable
```
Dans un second dictionnaire supprimer cette vérification :
```xml
<variables>
<variable name="my_variable" redefine="True" remove_check="True"/>
</variables>
```
En YAML :
```yml
variables:
- variable:
name: my_variable
redefine: true
remove_check: true
```

View file

@ -1,5 +1,125 @@
# Les conditions
---
gitea: none
include_toc: true
---
- [Déclaration d'une condition](condition.md)
- [Les différentes conditions](conditions.md)
- [Réfinition](redefine.md)
# 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,129 +0,0 @@
# Les conditions
## Un condition
Les conditions permettent d'ajouter ou de supprimer des propriétés à une [variable](../variable/README.md), une [famille](../family/README.md), un [service](../service/README.md), un [fichier](../service/file.md) ou une [ip](../service/ip.md) suivant le contexte.
Nous allons nous concentrer ici sur la condition hidden_if_in, mais [il existe d'autre conditions](conditions.md).
La condition hidden_if_in permet de cacher une variable où une famille à l'utilisateur, mais cette variable est toujours accessible dans un calcul, un vérification ou dans un template.
```xml
<variables>
<variable name="condition" type="boolean"/>
<variable name="my_variable"/>
</variables>
<constraints>
<condition name="hidden_if_in" source="condition">
<param>True</param>
<target>my_variable</target>
</condition>
</constraints>
```
En YAML :
```yml
variables:
- variable:
name: condition
type: boolean
- variable:
name: my_variable
constraints:
- condition:
- name: hidden_if_in
source: condition
param:
- text: true
target:
- text: my_variable
```
Le [paramètres](../param/README.md) de la condition permet de définir les valeurs que doit avoir la source pour appliquer l'action.
La [cible](../target/README.md) de la condition est ici "my_variable".
Donc ici la variable est caché à l'utilisateur si la variable "condition" est à True (le paramètre).
## Un condition avec plusieurs paramètres
Il est également possible de mettre plusieurs paramètre :
```xml
<variables>
<variable name="condition"/>
<variable name="my_variable"/>
</variables>
<constraints>
<condition name="hidden_if_in" source="condition">
<param>yes</param>
<param>maybe</param>
<target>my_variable</target>
</condition>
</constraints>
```
En YAML :
```yml
variables:
- variable:
name: condition
- variable:
name: my_variable
constraints:
- condition:
- name: hidden_if_in
source: condition
param:
- text: 'yes'
- text: 'maybe'
target:
- text: my_variable
```
## Une condition optionnelle
Il est possible de définir une condition avec une variable source qui n'existe pas dans toutes les contextes.
Dans ce cas, on met la condition en "optionnelle".
Si la variable source existe, la condition s'applique.
Si la variable source n'existe pas :
- si le nom fini en _if_in (par exemple hidden_if_in), l'action est forcée sans condition (les cibles sont hidden)
- si le nom fini en _if_not_in (par exemple hidden_if_not_in), la condition est totalement ignorée
Ces deux comportements peuvent être changé à tout moment avec l'attribut "apply_on_fallback". Dans ce cas :
- si la valeur de "apply_on_fallback" est "True", l'action est forcée sans condition
- si la valeur de "apply_on_fallback" est "False", la condition est totalement ignorée
Exemple :
```xml
<condition name="hidden_if_in" source="condition" optional="True", apply_on_fallback="False">
<param>yes</param>
<param>maybe</param>
<target>my_variable</target>
</condition>
```
En YAML :
```yml
- condition:
- name: hidden_if_in
source: condition
optional: true
apply_on_fallback: false
param:
- text: 'yes'
- text: 'maybe'
target:
- text: my_variable
```

View file

@ -1,29 +0,0 @@
# Les conditions
## Les conditions \_if_in et \_if_not_in
Il existe deux types de conditions :
- les conditions dont le nom fini par \_if_in : dans ce cas si la variable source est égal à un des paramètres, l'action est effective
- les conditions dont le nom fini par \_if_not_in : dans ce cas si la variable source est différents de tous les paramètres, l'action est effective
## Désactivation
Il est possible de désactiver une [variable](../variable/README.md) ou une [famille](../family/README.md) avec les conditions :
- disabled_if_in
- disabled_if_not_in
## Caché
Il est possible de cacher une [variable](../variable/README.md), une [famille](../family/README.md), un [service](../service/README.md), un [fichier](../service/file.md) ou une [ip](../service/ip.md) avec les conditions :
- hidden_if_in
- hidden_if_not_in
## Obligatoire
Il est possible de rendre obligatoire une [variable](../variable/README.md) avec les conditions :
- mandatory_if_in
- mandatory_if_not_in

View file

@ -1,58 +0,0 @@
# Rédéfinition
Il se peut que dans un dictionnaire on décide de définir une condition.
Dans un second dictionnaire il est possible de supprimer cette condition.
Dans un premier dictionnaire déclarons notre variable et notre calcule :
```xml
<variables>
<variable name="condition" type="boolean"/>
<variable name="my_variable"/>
</variables>
<constraints>
<condition name="hidden_if_in" source="condition">
<param>True</param>
<target>my_variable</target>
</condition>
</constraints>
```
En YAML :
```yml
variables:
- variable:
name: condition
type: boolean
- variable:
name: my_variable
constraints:
- condition:
- name: hidden_if_in
source: condition
param:
- text: true
target:
- text: my_variable
```
Dans un second dictionnaire supprimer ce calcul :
```xml
<variables>
<variable name="condition" redefine="True" remove_condition="True"/>
</variables>
```
En YAML :
```yml
variables:
- variable:
name: condition
redefine: true
remove_condition: true
```

View file

@ -1,58 +1,42 @@
# La bibliothèque Rougail
Rougail est une bibliothèque qui permet de charger simplement des dictionnaires et de générer des templates.
Rougail est une bibliothèque de gestion de configuration qui permet de charger simplement des variables.
Dans les exemples suivant, nous utiliserons une configuration particulière de Rougail.
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).
Le script contiendra donc les éléments de configuration suivant :
Pour charger la configuration il faut importer la variable RougailConfig et changer les valeurs :
```python
from rougail import RougailConfig
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['templates_dir'] = ['tmpl']
RougailConfig['tmp_dir'] = 'tmp'
RougailConfig['destinations_dir'] = 'dest'
RougailConfig['functions_file'] = 'funcs/functions.py'
```
Penser a créer les répertoires :
```bash
$ mkdir dest dict tmp tmpl extras
```
## Convertisons un dictionnaire
Un dictionnaire est un ensemble d'instruction qui vont permettre de créer des variables.
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: '0.10'
variables:
- variable:
- name: my_variable
value:
- text: my_value
---
version: '1.0'
my_variable:
default: my_value
```
Puis, créons les objets [Tiramisu](https://framagit.org/tiramisu/tiramisu) :
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
from asyncio import run
async def main():
RougailConfig['dictionaries_dir'] = ['dict']
rougail = Rougail()
config = await rougail.get_config()
print(await config.value.dict())
run(main())
RougailConfig['dictionaries_dir'] = ['dict']
rougail = Rougail()
config = rougail.get_config()
print(config.value.get())
```
Exécution le script :
@ -78,28 +62,22 @@ RougailConfig['extra_dictionaries']['example'] = ['extras/']
Ensuite créons un dictionnaire extra extras/00-base.yml :
```yml
version: '0.10'
variables:
- variable:
- name: my_variable_extra
value:
- text: my_value_extra
---
version: '1.0'
my_variable_extra:
default: my_value_extra
```
Construisons les objets Tiramisu :
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
from asyncio import run
async def main():
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['extra_dictionaries']['example'] = ['extras/']
rougail = Rougail()
config = await rougail.get_config()
print(await config.value.dict())
run(main())
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['extra_dictionaries']['example'] = ['extras/']
rougail = Rougail()
config = rougail.get_config()
print(config.value.dict())
```
Exécution le script :
@ -109,67 +87,18 @@ $ python3 script.py
{'rougail.my_variable': 'my_value', 'example.my_variable_extra': 'my_value_extra'}
```
## Templatisons un fichier
Un [template](../template/README.md) est un fichier dans lequel on va remplacer les valeurs attendues par le nom des variables.
Premièrement déclarons dans un dictionnaire complémentaire notre template dict/00-template.yml :
```yml
version: '0.10'
services:
- service:
- name: test
file:
- text: /etc/example.conf
```
Et un template tmpl/example.conf (par défaut il est généré via une configuration particulière de [Cheetah](https://cheetahtemplate.org/) :
```
The value: %%my_variable
The extra value: %%example.my_variable_extra
```
Générons le template :
```python
from rougail import Rougail, RougailConfig
from asyncio import run
async def main():
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['templates_dir'] = ['tmpl']
RougailConfig['tmp_dir'] = 'tmp'
RougailConfig['destinations_dir'] = 'dest'
RougailConfig['extra_dictionaries']['example'] = ['extras/']
RougailConfig['functions_file'] = 'funcs/functions.py'
rougail = Rougail()
await rougail.template()
run(main())
```
Le fichier dest/etc/example.conf est maintenant créé avec le contenu attendu suivant :
```
The value: my_value
The extra value: my_value_extra
```
## Créons une fonction personnalisé
Nous créons le dictionnaire complémentaire dict/00-fill.yml pour que la variable "my_variable" soit [calculée](fill/README.md) :
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: '0.10'
constraints:
- fill:
- name: return_no
target:
- text: my_variable
---
version: '1.0'
my_variable_jinja:
type: "string"
default:
type: jinja
jinja: "{{ return_no() }}"
```
Puis créons la fonction "return_no" dans functions.py :
@ -179,41 +108,24 @@ def return_no():
return 'no'
```
Après avoir reconverti les dictionnaires et regénérer le template nous avons donc le contenu du fichier dest/etc/example.conf :
```
The value: no
The extra value: my_value_extra
```
La valeur de la variable "my_variable" est bien calculé à partir de la fonction "return_no".
## Template et systemd
Rougail peut également généré automatiquement le fichier [tmpfiles.d](https://www.freedesktop.org/software/systemd/man/tmpfiles.d.html) pour installer automatiquement les fichiers de configuration au démarrage de la machine.
Pour générer le fichier tmpfiles.d, ajouter l'argument "systemd" à la methode "template" :
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
from asyncio import run
async def main():
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['templates_dir'] = ['tmpl']
RougailConfig['tmp_dir'] = 'tmp'
RougailConfig['destinations_dir'] = 'dest'
RougailConfig['extra_dictionaries']['example'] = ['extras/']
RougailConfig['functions_file'] = 'funcs/functions.py'
rougail = Rougail()
await rougail.template('systemd')
run(main())
RougailConfig['dictionaries_dir'] = ['dict']
RougailConfig['extra_dictionaries']['example'] = ['extras/']
RougailConfig['functions_file'] = 'functions.py'
rougail = Rougail()
config = rougail.get_config()
print(config.value.dict())
```
Ainsi le fichier supplémentaire "dest/tmpfiles.d/0rougail.conf" sera créé avec le contenu :
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'}
```
C /etc/example.conf 0644 root root - /usr/local/lib/etc/example.conf
```
La valeur de la variable `my_variable_extra` est bien calculé à partir de la fonction `return_no`.

View file

@ -1,6 +1,6 @@
# Personnalisons la configuration de Rougail
La configuration de rougail se trouve dans l'objet RougailConfig :
La configuration de Rougail se trouve dans l'objet `RougailConfig` :
```python
from rougail import RougailConfig
@ -14,29 +14,17 @@ Pour modifier il suffit de faire :
RougailConfig[key] = value
```
## Ajout d'une fonction de conversion
Les fonctions de conversion fait parti 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.
## 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 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 sont chargés dans l'ordre des répertoires. Chaque répertoire est chargé les uns après les autres. A l'intérieur de ces répertoires les fichiers XML ou YAML seront classés par ordre alphabétique.
- 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.
Il n'y a pas de classement par ordre alphabétique de l'ensemble des fichiers XML ou YAML de tous les répertoires.
Les familles et variables de ces dictionnaires sont classés, par défaut, dans l'espace de nom "rougail". Il est possible de changer le nom de cet espace de nom avec la clef "variable_namespace".
- 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 :
Par exemple pour ajouter l'extra `example` il faut faire :
```python
RougailConfig['extra_dictionaries']['example'] = ['/dir1', '/dir2']
@ -44,14 +32,6 @@ RougailConfig['extra_dictionaries']['example'] = ['/dir1', '/dir2']
Les dictionnaires sont chargés dans le même ordre que les dictionnaires principaux.
### La DTD et le schema YAML
Rougail a besoin du fichier de la DTD pour lire les fichiers dictionnaire de type XML et du schema YAML pour les fichiers dictionnaire de type YAML.
Par défaut le fichier de la DTD et le schema YAML sont dans le sous répertoire "data" du répertoire de code. Le nom du fichier est rougail.dtd et rougail.yml.
Pour pouvez changer le nom du fichier DTD avec la clef "dtdfilename" et le nom du schema YAML avec la clef "yamlschema_filename".
### 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.
@ -106,44 +86,9 @@ Le répertoire de destination des fichiers générés est géré dans la clef "d
Un certain nombre de variables concerne les templates systemd.
#### Les services
## Ajout d'une fonction de conversion
Les services sont générés dans le sous-répertoire de la clef "systemd_service_directory" (par défault "/systemd") du répertoire "destinations_dir" (voir plus haut) .
Les fonctions de conversion fait parti du moteur de rougail. Il converti les informations des dictionnaires pour créer des variables Tiramisu.
Par contre la racine de ce sous répertoire sur le système finale sera la valeur de la clef "systemd_service_destination_directory" (par défaut "/usr/local/lib").
Les autres services, non généré via Rougail, devront être dans le répertoire de la clef "system_service_directory" (par défault "/usr/lib/systemd/system").
La clef "extra_annotators" permet d'ajouter des fonctions complémentaires.
Lorsqu'on [réécrit un service](../service/override.md) le fichier définit dans la clef "systemd_service_file" (par défaut "rougail.conf").
Les [IP](../service/ip.md) seront dans le fichier définit dans la clef "systemd_service_ip_file" (par défaut "rougail_ip.conf").
### Les tmpfiles
Les fichiers tmpfiles sont générés dans le sous-répertoire de la clef "systemd_tmpfile_directory" (par défault "/tmpfiles.d") du répertoire "destinations_dir" (voir plus haut) .
Par contre la racine de ce sous répertoire sur le système finale sera la valeur de la clef "systemd_tmpfile_factory_dir" (par défaut "/usr/local/lib").
Le fichier généré dans ce répertoire aura le nom définit dans la clef "systemd_tmpfile_file", donc "0rougail.conf" par défaut.
## La configuration par défaut des fichiers
### Le moteur de templates par défaut
Le moteur de template est géré dans la clef "default_files_engine" et a comme valeur par défaut : "cheetah". Les valeurs possible sont "none", "cheetah" ou "jinja".
### Les droits par défaut des fichiers
Les droits des fichiers générés est géré dans la clef "default_files_mode" (valeur de type nombre) et a comme valeur par défaut : 644.
### Le propriétaire par défaut des fichiers
Le propriétaire des fichiers générés est géré dans la clef "default_files_owner" et a comme valeur par défaut : "root".
Le groupe propriétaire des fichiers générés est géré dans la clef "default_files_group" et a comme valeur par défaut : "root".
### La méthode d'inclusion par défaut des fichiers
La méthode d'inclusion des fichiers générés est géré dans la clef "default_files_included" et a comme valeur par défaut : "no". Les valeurs possible sont "no", "name" et "content".
'default_files_included': 'no',
## La configuration du moteur de templates
Le moteur de template est géré dans la clef "default_overrides_engine" et a comme valeur par défaut : "cheetah". Les valeurs possible sont "none", "cheetah" ou "jinja".

View file

@ -1,21 +1,20 @@
# Convention de rédaction d'un dictionnaire
---
gitea: none
include_toc: true
---
## Ordonnancement du dictionnaire
# Conventions
L'ordre des informations mise dans le dictionnaire est idéalement :
- services
- variables
- constraintes
## Nom des fichiers de dictionnaire
## 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
Par exemple : `00-base.xml`
## Le nombre d'espace XML
## Convention du nom des familles et variables
Le nombre d'espace dans un dictionnaire au format XML est de deux espaces.
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,12 +0,0 @@
# 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 :
- des variables et des familles peuvent avoir le même nom dans différentes familles
- la valeur d'un cible, source, leader ou follower des contraintes doivent être avec un chemin complet
- on ne peut pas déclarer des services dans cet espace de nom
- dans un template il faut utiliser des chemins complet (%%my_extra.my_family.my_variable ou %%my_extra.my_family.leader.follower pour une variable suiveuse)

View file

@ -1,22 +1,34 @@
---
gitea: none
include_toc: true
---
# Les dictionnaires
## Un dictionnaire ?
Un dictionnaire est un fichier XML ou YAML donc la structure est décrite dans cette documentation.
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, utilisable à tout moment, notamment dans des templates.
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, les variables et les contraintes peuvent être défini dans plusieurs dictionnaires. Ces dictionnaires s'aggrège alors.
Les familles et les variables peuvent être définis dans plusieurs dictionnaires. Ces dictionnaires s'aggrègent alors.
Il est également possible de redéfinir des éléments pour changer les comportement d'une variable ou d'un service.
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
L'espace de nom par défaut s'appelle "rougail" ([ce nom est personnalisable](../dev/config.md)).
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 :
Cet espace de nom est un peu particulier, il peut accéder a des variables dans un autre espace de nom.
- le nom des variables et des familles doivent être unique pour l'ensemble de cet espace (même si ces variables ou familles sont dans des familles différentes)
- la valeur d'un cible, source, leader ou follower des contraintes peuvent être avec nom de la variable ou de la famille ou leurs chemins complet
- on peut déclarer des services dans cet espace de nom
- dans un template on peut utiliser cette variable sans le chemin complet (%%my_variable) ou avec (%%rougail.my_family.my_variable)
## 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,6 +1,144 @@
# Famille
---
gitea: none
include_toc: true
---
- [Une famille](simple.md)
- [Famille crée dynamiquement](auto.md)
- [Les variables meneuses ou suiveuses](leadership.md)
# 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é à une variable.
Le nom et la description de la famille et des variables qu'elle contient sera en réalité le prefix du nouveau nom/description. Le suffix viendra de la valeur de la variable liée.
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
```
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\_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,46 +0,0 @@
# Famille crée dynamiquement
Pour créer une famille dynamiquement, il faut créer une famille fictive lié à une variable.
Le nom et la description de la famille et des variables qu'elle contient sera en réalité le prefix du nouveau nom/description. Le suffix viendra de la variable liée.
Par exemple :
```xml
<variable name='varname' multi="True">
<value>val1</value>
<value>val2</value>
</variable>
<family name="my_dyn_family_" dynamic="varname" description="Describe ">
<variable name="my_dyn_var_"/>
</family>
```
En YAML :
```yml
variables:
- variable:
name: variable
multi: true
value:
- text: 'val1'
- text: 'val2'
- family:
name: my_dyn_family_
dynamic: varname
description: 'Describe '
variables:
- variable:
name: my_dyn_var_
```
Créera deux familles :
- la famille dynamique : "my_dyn_family_val1" avec la description "Describe val1"
- la famille dynamique : "my_dyn_family_val2" avec la description "Describe val2"
Dans la famille dynamique "my_dyn_family_val1" on retrouvera une variable "my_dyn_var_val1".
Bien évidement si le contenu de "varname" venait a évolué, de nouvelles familles dynamiques pouvent apparaitre ou des familles dynamiques peuvent disparaître.
Attention la variable lié à la famille doit être obligatoirement une variable multiple et il n'est pas possible de mettre une famille dans une famille dynamique.

View file

@ -1,85 +0,0 @@
# Variable meneuse ou suiveuse
## Variable meneuse
Une variable meneuse est une variable qui va guider la longueur d'autre variables (appelé variables suiveuse).
Une variable meneuse est une [variable](../variable/README.md) qui est obligatoirement de type multiple.
Une variable meneuse peut être obligatoire.
Le [mode](../mode.md) par défaut correspond au plus petit mode définit par l'utilisateur des 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 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](../mode.md) par défaut d'une variable suiveuse correspond au [mode](../mode.md) de la variable meneuse.
Si une variable meneuse est caché ou désactivé, les variables suiveuses le seront également.
## Définition des variables meneuse et suiveuse
Les variables meneuses et suiveuses doivent dans une famille de type "leadership".
Voici un exemple de définition d'une variable meneuse et de deux variables meneuses :
```xml
<variables>
<family name="family" leadership='True'>
<variable name="leader" multi='True'/>
<variable name="follower1"/>
<variable name="follower2" multi='True'/>
</family>
</variables>
```
En YAML :
```yml
variables:
- family:
name: family
leadership: true
variables:
- variable:
name: leader
multi: true
- variable:
name: follower1
- variable:
name: follower2
multi: true
```
## Ajout d'une nouvelle variable suiveuse
Pour ajouter, dans un nouveau dictionnaire, une variable suiveuse à notre groupe, rien de plus simple, il suffit définir une ou des nouvelles variables dans la famille :
```xml
<variables>
<family name="family">
<variable name="follower3"/>
</family>
</variables>
```
En YAML :
```yml
variables:
- family:
name: family
variables:
- variable:
name: follower3
```

View file

@ -1,128 +0,0 @@
# Une famille
Une famille est un conteneur de variables. Elle peut contenir également des familles.
Pour décrire une famille il faut mettre au minimum un nom :
```xml
<family name="my_family"/>
```
En YAML :
```yml
- family:
name: my_family
```
Cette famille doit être placé dans une balise [variables](../variables.md) :
```xml
<variables>
<family name="my_family"/>
</variables>
```
En YAML :
```yml
variables:
- family:
name: my_family
```
Ou dans une autre famille :
```xml
<variables>
<family name="my_family">
<family name="second_family"/>
</family>
</variables>
```
En YAML :
```yml
variables:
- family:
name: my_family
variables:
- family:
name: second_family
```
Attention, une famille vide sera automatiquement supprimée.
## Description et aide de la famille
En plus d'un nom, il est possible de mettre une "description" à la famille. C'est une information "utilisateur" qui nous permettra d'avoir des informations complémentaires sur le contenu de cette famille :
```xml
<family name="my_family" description="This is a great family"/>
```
En YAML :
```yml
variables:
- family:
name: my_family
description: 'This is a great family'
```
En plus de la description, il est possible de préciser une aide complémentaire :
```xml
<family name="my_family" help="This is a great family"/>
```
En YAML :
```yml
variables:
- family:
name: my_family
help: 'This is a great family'
```
## Mode de la famille
Le [mode](../mode.md) par défaut d'une famille correspond au [mode](../mode.md) du mode le plus petit entre la famille parente, les variables enfants ou des familles enfants qui sont contenu dans cette famille.
Changer le [mode](../mode.md) d'une famille permet de définir le [mode](../mode.md) par défaut des variables ou des familles inclusent dans cette famille.
Pour définir le [mode](../mode.md) :
```xml
<family name="my_family" mode="expert"/>
```
En YAML :
```yml
- family:
name: my_family
mode: expert
```
## Famille invisible
Il est possible de cacher une famille, ainsi que toutes les variables et des familles inclusent dans cette famille.
Cacher une famille signifie qu'elle ne sera pas visible lorsqu'on modifie la configuration du service.
Par contre ces variables sont accessibles lorsqu'on va utiliser ces variables.
Pour cacher une famille :
```xml
<family name="my_family" hidden="True"/>
```
En YAML :
```yml
- family:
name: my_family
hidden: true
```

View file

@ -1,7 +1,368 @@
# Les variables calculées
---
gitea: none
include_toc: true
---
Une variable calculée est une variable donc sa valeur est le résultat d'une fonction python.
# Les valeurs par défault calculées
- [Valeur calculée de la variable](value.md)
- [Réfinition](redefine.md)
- [Exemples de calcule](examples.md)
## 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
```
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_val1
```
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,98 +0,0 @@
# Exemples de calcule
## Calculer les noms de modèle à généré à partir d'une variable
Créeons deux variables multiples, une pour accueillir la liste des éléments du nom variable (ici `zones_list`), la seconde étant le nom du fichier calculé (ici `netwokd_configurations`) :
```xml
<variables>
<variable name="zones_list" type="string" multi="True">
<value>zone1</value>
<value>zone2</value>
<value>zone3</value>
</variable>
<variable name="netwokd_configurations" type="filename" multi="True" hidden="True"/>
</variables>
```
En YAML :
```yml
variables:
- variable:
name: zones_list
type: string
multi: true
value:
- text: 'zone1'
- text: 'zone2'
- text: 'zone3'
- variable:
name: netwokd_configurations
type: filename
multi: true
hidden: true
```
Calculons la valeur de la seconde variable à partir de la première :
```xml
<constraints>
<fill name="calc_value">
<param>/systemd/network/10-</param>
<param type="variable">zones_list</param>
<param>-risotto.network</param>
<param name="join"></param>
<param name="multi" type="boolean">True</param>
<target>netwokd_configurations</target>
</fill>
</constraints>
```
En YAML :
```yml
constraints:
- fill:
- name: calc_value
param:
- text: /systemd/network/10-
- type: variable
text: zones_list
- text: -risotto.network
- name: join
- name: multi
type: boolean
text: true
target:
- text: netwokd_configurations
```
Le contenu de la variable `netwokd_configurations` sera alors :
- /systemd/netword/10-zone1/risotto.network
- /systemd/netword/10-zone2/risotto.network
- /systemd/netword/10-zone3/risotto.network
Enfin déclarer une balise file en utilisant ces deux variables :
```xml
<services>
<service name="systemd-networkd">
<file file_type="variable" source="network" variable="zones_list">netwokd_configurations</file>
</service>
</services>
```
En YAML :
```yml
services:
- service:
- name: systemd-networkd
file:
- file_type: variable
source: network
variable: zones_list
text: netwokd_configurations
```

View file

@ -1,108 +0,0 @@
# Rédéfinition
## Redéfinition des calcules
Dans un premier dictionnaire déclarons notre variable et notre calcule :
```xml
<variables>
<variable name="my_calculated_variable"/>
</variables>
<constraints>
<fill name="return_no">
<target>my_calculated_variable</target>
</fill>
</constraints>
```
En YAML :
```yml
variables:
- variable:
name: my_calculated_variable
constraints:
- fill:
- name: return_no
target:
- text: my_calculated_variable
```
Dans un second dictionnaire il est possible de redéfinir le calcul :
```xml
<variables>
<variable name="my_calculated_variable" redefine="True"/>
</variables>
<constraints>
<fill name="return_yes">
<target>my_calculated_variable</target>
</fill>
</constraints>
```
En YAML :
```yml
variables:
- variable:
name: my_calculated_variable
redefine: true
constraints:
- fill:
- name: return_yes
target:
- text: my_calculated_variable
```
Dans ce cas, à aucun moment la fonction "return_no" ne sera exécuté. Seul la fonction "return_yes" le sera.
## Redéfinition avec suppression d'un calcul
Il se peut que dans un dictionnaire on décide de définir une valeur par défaut à une variable via un calcul.
Dans un second dictionnaire il est possible de supprimer ce calcul.
Dans un premier dictionnaire déclarons notre variable et notre calcule :
```xml
<variables>
<variable name="my_calculated_variable"/>
</variables>
<constraints>
<fill name="return_no">
<target>my_calculated_variable"</target>
</fill>
</constraints>
```
En YAML :
```yml
variables:
- variable:
name: my_calculated_variable
constraints:
- fill:
- name: return_no
target:
- text: my_calculated_variable
```
Dans un second dictionnaire supprimer ce calcul :
```xml
<variables>
<variable name="my_calculated_variable" redefine="True" remove_fill="True"/>
</variables>
```
En YAML :
```yml
variables:
- variable:
name: my_calculated_variable
redefine: true
remove_fill: true
```

View file

@ -1,197 +0,0 @@
# Valeur calculée de la variable
## Variable avec une valeur par défaut calculée
Créons une variable dont la valeur est retournée par la fonction "return_no" :
```xml
<variables>
<variable name="my_calculated_variable"/>
</variables>
<constraints>
<fill name="return_no">
<target>my_calculated_variable</target>
</fill>
</constraints>
```
En YAML :
```yml
variables:
- variable:
name: my_calculated_variable
constraints:
- fill:
- name: return_no
target:
- text: my_calculated_variable
```
Puis créons la fonction "return_no" :
```python
def return_no():
return 'no'
```
La [cible (de type variable)](../target/variable.md) du calcul est ici "my_calculated_variable".
Dans ce cas, la valeur par défaut est la valeur retournée par la fonction (ici "no"), elle sera calculée tant que l'utilisateur n'a pas de spécifié de valeur à cette variable.
Attention, si une valeur par défaut est définit dans la variable "my_calculated_variable" :
```xml
<variable name="my_calculated_variable">
<value>yes</value>
</variable>
```
En YAML :
```yml
variables:
- variable:
name: my_calculated_variable
value:
- text: yes
```
Cette valeur par défaut sera complètement ignorée. C'est le calcul qui en définira la valeur.
Il est possible de définir des [paramètres](../param/README.md) à cette fonction.
## Variable avec une valeur calculée
En ajoutant le paramètre "hidden" à "True" dans la variable précédente, l'utilisateur n'aura plus la possibilité de modifié la valeur. La valeur de la variable sera donc systématiquement calculée :
```xml
<variable name="my_calculated_variable" hidden="True"/>
```
En YAML :
```yml
variables:
- variable:
name: my_calculated_variable
hidden: true
```
Si une condition "hidden_if_in" est spécifié à la variable, la valeur sera modifiable par l'utilisateur si elle n'est pas cachée mais elle sera systèmatiquement calculée (même si elle a déjà était modifiée) si la variable est cachée.
## Variable meneuse ou suiveuse avec valeur calculé
Une [variable suiveuse](../family/leadership.md) ne peut pas être calculé automatiquement.
Une [variable meneuse](../family/leadership.md) peut être calculé automatiquement.
Si la variable n'est pas multiple, il ne faut pas que le calcule retourne une liste.
## Variable dynamique avec une valeur calculée
Il est également possible de calculer [une variable d'une famille dynamique](../family/auto.md) à partir d'une variable standard :
```xml
<variables>
<variable name='suffixes' type='string' description="Suffixes of dynamic family" multi="True">
<value>val1</value>
<value>val2</value>
</variable>
<variable name="my_variable" type="string" description="My variable">
<value>val</value>
</variable>
<family name='dyn' dynamic="suffixes">
<variable name="my_calculated_variable_dyn_" type="string" description="My calculated variable"/>
<value>val</value>
</variable>
</family>
</variables>
<constraints>
<fill name="return_value">
<param type="variable">my_variable</param>
<target>my_calculated_variable_dyn_</target>
</fill>
</constraints>
```
En YAML :
```yml
variables:
- variable:
name: suffixes
type: string
description: Suffixes of dynamic family
multi: true
value:
- text: val1
- text: val2
- variable:
name: my_variable
type: string
description: My variable
value:
- text: val
- family:
name: dyn
dynamic: suffixes
variables:
- variable:
name: my_calculated_variable_dyn_
type: string
description: My calculated variable
value:
- text: val
constraints:
- fill:
- name: return_value
param:
- type: variable
text: my_variable
target:
- text: my_calculated_variable_dyn_
```
Dans ce cas, les variables dynamiques "my_calculated_variable_dyn_" seront calculés à partir de la valeur de la variable "my_variable".
Que cela soit pour la variable "my_calculated_variable_dyn_val1" et "my_calculated_variable_dyn_val2".
Par contre, il n'est pas possible de faire un calcul pour une seule des deux variables issues de la variable dynamique.
Si c'est ce que vous cherchez à faire, il faudra prévoir un traitement particulier dans votre fonction.
Dans ce cas, il faut explicitement demander la valeur du suffix dans la fonction :
```xml
<constraints>
<fill name="return_value_suffix">
<param type="variable">my_variable</param>
<param type="suffix"/>
<target>my_calculated_variable_dyn_</target>
</fill>
</constraints>
```
En YAML :
```yml
constraints:
- fill:
- name: return_value_suffix
param:
- type: variable
text: my_variable
- type:suffix
target:
- text: my_calculated_variable_dyn_
```
Et ainsi faire un traitement spécifique pour ce suffix :
```python
def return_value_suffix(value, suffix):
if suffix == 'val1':
return value
```
## Variable avec valeur calculée obligatoire
Par défaut les variables calculées ne sont pas des variables obligatoires.
Dans ce cas un calcul peut retourner "None" ou "", mais surtout un utilisateur peut spécifier une valeur nulle à cette variable. Dans ce cas le calcul ne sera plus réalisé.

BIN
doc/firefox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

BIN
doc/foxyproxy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

698
doc/getting_started.md Normal file
View file

@ -0,0 +1,698 @@
---
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,10 +0,0 @@
Mode
====
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 toute connaissance de cause
Il est possible de personnaliser les modes dans la [configuration de rougail](dev/config.md)

View file

@ -1,7 +0,0 @@
# Paramètre de la fonction
- [Paramètre positionnel ou nommée](positional.md)
- [Type de paramètre simple](simple.md)
- [Type de paramètre "variable"](variable.md)
- [Type de paramètre "information"](information.md)

View file

@ -1,68 +0,0 @@
# Paramètre de type information
## Les informations de la configuration
Le paramètre peut être la valeur est issue d'une information de la configuration :
```xml
<param type="information">server_name</param>
```
En YAML :
```yml
param:
- type: information
text: server_name
```
Dans ce cas, l'information de la configuration "server_name" sera utilisé comme valeur du paramètre.
Si l'information n'existe pas, la paramètre aura la valeur "None" ou [] pour une variable multiple.
## Les informations d'une variable
Le paramètre peut être la valeur est issue d'une information d'une variable :
```xml
<param type="information" variable="a_variable">test</param>
<param type="information" variable="a_variable">help</param>
```
En YAML :
```yml
param:
- type: information
variable: a_variable
text: test
- type: information
variable: a_variable
text: help
```
Dans ce cas, l'information de la variable "a_variable" "test" ou "help" sera utilisée comme valeur du paramètre.
Si l'information n'existe pas, la paramètre aura la valeur "None" ou [] pour une variable multiple.
## Les informations de la cible
Le paramètre peut être la valeur est issue d'une information de la cible du calcul (la target) :
```xml
<param type="information" variable="target_variable">test</param>
<param type="information" variable="target_variable">help</param>
```
En YAML :
```yml
param:
- type: information
variable: target_variable
text: test
- type: information
variable: target_variable
text: help
```
Dans ce cas, l'information de la variable de la cible (target_variable) "test" ou "help" sera utilisée comme valeur du paramètre.
Si l'information n'existe pas, la paramètre aura la valeur "None" ou [] pour une variable multiple.

View file

@ -1,41 +0,0 @@
# Paramètre positionnel
Déclarons un paramètre positionnel :
```xml
<param>no</param>
```
En YAML :
```yml
param:
- text: no
```
Créons la fonction correspondante :
```python
def return_value(value):
return value
```
La variable "value" de la fonction "return_value" aura donc "no" comme valeur puisque le paramètre aura la valeur fixe "no".
# Paramètre nommée
Déclarons maintenant un paramètre nommée :
```xml
<param name="valeur">no</param>
```
En YAML :
```yml
param:
- name: valeur
text: no
```
Dans ce cas la fonction return_value sera exécuté avec le paramètre nommé "valeur" dont sa valeur sera "no".

View file

@ -1,90 +0,0 @@
# Paramètre de type "texte"
Déclarons un paramètre avec une string :
```xml
<param type="string">no</param>
```
En YAML :
```yml
param:
- type: string
text: no
```
C'est le type par défaut pour un paramètre.
# Paramètre de type "nombre"
Déclarons un paramètre avec un nombre :
```xml
<param type="number">1</param>
```
En YAML :
```yml
param:
- type: number
text: 1
```
Créons la fonction correspondante :
```python
def return_value_with_number(value):
if value == 1:
return 'no'
return 'yes'
```
La variable aura donc "no" comme valeur puisque le paramètre aura la valeur fixe "1".
# Paramètre de type "booléen"
Déclarons un paramètre avec un booléen :
```xml
<param type="boolean">True</param>
```
En YAML :
```yml
param:
- type: boolean
text: true
```
# Paramètre de type "nil"
Le paramètre peut être une valeur null (None en python) :
```xml
<param type="nil"/>
```
En YAML :
```yml
param:
- type: nil
```
# Paramètre de type "space"
Les paramètres sont chargés en supprimer les espaces en début ou fin de chaîne. Ce qui rend impossible d'avoir un paramètre " ". Avec le type "space", le paramètre sera donc un simple espace :
```xml
<param type="space"/>
```
En YAML :
```yml
param:
- type: space
```

View file

@ -1,96 +0,0 @@
# Paramètre de type "variable"
Imaginons que la variable "my_variable" pré-existe. La valeur de la variable sera utilisé comme paramètre :
```xml
<param type="variable">my_variable</param>
```
En YAML :
```yml
param:
- type: variable
text: my_variable
```
[Les variables meneuses ou suiveuses](../family/leadership.md) peuvent être utilisé sans soucis comme paramètre.
## Paramètre avec variable potentiellement non existante
Suivant le contexte une variable peut exister ou ne pas exister.
Un paramètre de type "variable" peut être "optional" :
```xml
<param type="variable" optional="True">unknow_variable</param>
```
En YAML :
```yml
param:
- type: variable
optional: true
text: unknow_variable
```
Si la variable "unknow_variable" n'existe pas, le paramètre ne sera pas passé à la fonction.
Si maintenant on créé un nouveau dictionnaire en créant cette variable, la fonction sera exécuté avec le paramètre.
## Paramètre avec variable potentiellement désactivée
Si une variable est désactivé, l'utilisation de cette variable peut poser problème.
Il est possible de ne pas générer d'erreur si une variable est désactivé en utilisant le paramètre "propertyerror" :
```xml
<param type="variable" propertyerror="False">variable1</param>
```
En YAML :
```yml
param:
- type: variable
propertyerror: false
text: variable1
```
Dans ce cas, si la variable est désactivé, le paramètre n'est jamais donnée à la fonction de destination.
## Paramètre avec variable dynamique
Il est possible de faire un calcul avec comme paramètre [une variable d'une famille dynamique](../family/auto.md) mais pour une suffix particulier.
Par exemple :
```xml
<param type="variable">vardynval1</param>
```
En YAML :
```yml
param:
- type: variable
text: vardynval1
```
Dans ce cas, la valeur du paramètre de la fonction sera la valeur de la variable "vardyn" pour la famille ayant le suffix "val1".
Il peut être utile de récupérer la valeur du suffix dans la fonction, pour cela il suffit de mettre un paramètre de type suffix :
```xml
<param type="suffix"/>
```
En YAML :
```yml
param:
- type: suffix
```
Dans l'exemple précédent la valeur de ce paramètre sera "val1".

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View file

@ -1,186 +0,0 @@
# La gestion d'un service
## La base service
Un service est inclut dans un conteneur [services](../services.md).
Cette balise permet de définir tous les éléments ([fichier](file.md), [certificat](certificate.md), [IP](ip.md) et [réécriture](override.md)) liés à un service ou à démon.
Il faut, à la création du service, préciser son nom :
```xml
<?xml version='1.0' encoding='UTF-8'?>
<rougail version="0.10">
<services>
<service name="squid"/>
</services>
</rougail>
```
En YAML :
```yml
version: '0.10'
services:
- service:
- name: squid
```
## Les types de service
Les services peuvent être de plusieurs type :
- service (valeur par défaut) : cela signifie que c'est un service systemd qui est activer au démarrage de la machine
- mount : fichier utilisé par systemd-mount
- swap : fichier utilisé par systemd-swap
- timer : tâche planifié pour systemd
```xml
<service name="dev-disk-by\x2dpartlabel-swap" type="swap"/>
```
En YAML :
```yml
- service:
- name: dev-disk-by\x2dpartlabel-swap
type: swap
```
## Les targets de service
Active le service systemd pour une target systemd déterminé. Par défaut, l'activation du service n'est pas gérer par rougail.
```xml
<service name="squid" target="multi-user"/>
```
En YAML :
```yml
- service:
- name: squid
target: multi-user
```
## La génération du fichier service
Le fichier de description du service peut être fourni directement par la distribution GNU/Linux utilisé, mais il peut également être fournit par l'administrateur.
Dans ce cas, il est possible de créé un template, dont le nom est obligatoirement la valeur de la balise "name" + "." + la valeur de la base "type".
Deux types de template sont aujourd'hui disponible :
- cheetah
- jinja
```xml
<service name="dev-disk-by\x2dpartlabel-swap" type="swap" engine="cheetah"/>
```
En YAML :
```yml
- service:
- name: dev-disk-by\x2dpartlabel-swap
type: swap
engine: cheetah
```
Dans ce cas, rougail utilisera le template "dev-disk-by\x2dpartlabel-swap.swap" pour générer le fichier systemd de gestion de ce service.
## Le service factice
Un service peut être factice, donc non géré par le système de service du système :
```xml
<service name="ldap_client" manage="False"/>
```
En YAML :
```yml
- service:
- name: ldap_client
manage: false
```
Un service factice est généralement une service qui n'existe pas réellement (par exemple si on configure un client). Il n'est là que pour contenir des fichiers.
## Désactiver le service
Il est possible de désactiver un service. Pour cela il faut rajouter l'attribut "disabled" à True :
```xml
<service name="test" disabled="True"/>
```
En YAML :
```yml
- service:
- name: test
disabled: true
```
Dans ce cas, le service et les éléments qu'il compose ([fichier](file.md), [certificat](certificate.md), [IP](ip.md) et [réécriture](override.md) seront désactivés.
Il est possible de définir une [condition](../condition/README.md) de type "disabled_if_in" ou "disabled_if_not_in" sur une balise service :
```xml
<services>
<service name="test" servicelist="test">
</service>
</services>
<variables>
<variable name="condition" type="boolean"/>
</variables>
<constraints>
<condition name="disabled_if_in" source="condition">
<param>False</param>
<target type="servicelist">test</target>
</condition>
</constraints>
```
En YAML :
```yml
services:
- service:
- name: test
servicelist: test
variables:
- variable:
- name: condition
type: boolean
constraints:
- condition:
- name: disabled_if_in
source: condition
param:
- text: false
target:
- type: servicelist
text: test
```
Dans ce cas, tous les services et les éléments qu'il compose avec un attribut servicelist à "test" seront désactivés si la variable "condition" est False.
## Ne pas désactiver le service dans systemd
La désactivation du service va créé un lien symbolique vers /dev/null.
Si vous ne voulez juste pas créer le fichier de service et ne pas faire de lien symbolique, il faut utiliser l'attribut undisable :
```xml
<service name="test" disabled="True" undisable="True"/>
```
En YAML :
```yml
- service:
- name: test
disabled: true
undisable: true
```

View file

@ -1,163 +0,0 @@
# La gestion d'un certificat
## La balise certificate
La gestion des certificats se fait dans un conteneur de [service](README.md).
La déclaration du certificat permet d'associer un certificat à un service. Attention, Rougail ne permet que de déclarer ces certificats. Il n'y a pas de gestion du certification dans la bibliothèque.
Pour déclarer un certificat :
```xml
<services>
<service name="squid">
<certificate private="/etc/pki/tls/private/squid.key" authority="/etc/pki/ca-trust/source/anchors/ca_squid.crt">/etc/pki/tls/certs/squid.crt</certificate>
</service>
</services>
```
En YAML :
```yml
services:
- service:
- name: squid
certificate:
- private: /etc/pki/tls/private/squid.key
authority: /etc/pki/ca-trust/source/anchors/ca_squid.crt
text: /etc/pki/tls/certs/squid.crt
```
Les trois informations a donner sont donc :
- le nom du certificat
- le nom de la clef privée
- le nom de certificat de l'autorité de certification
## Les noms de fichiers dynamique
Il est possible également de définir le nom des fichiers dans des variables :
```xml
<services>
<service name="squid">
<certificate private="private" private_type="variable" authority="authority" authority_type="variable" certificate_type="variable">certificate</certificate>
</service>
</services>
<variables>
<variable name="certificate" type="filename">
<value>/etc/pki/tls/certs/squid.crt</value>
</variable>
<variable name="private" type="filename">
<value>/etc/pki/tls/private/squid.key</value>
</variable>
<variable name="authority" type="filename">
<value>/etc/pki/ca-trust/source/anchors/ca_squid.crt</value>
</variable>
</variables>
```
En YAML :
```yml
services:
- service:
- name: squid
certificate:
- private: private
private_type: variable
authority: authority
authority_type: variable
certificate_type: variable
text: certificate
variables:
- variable:
- name: certificate
type: filename
value:
- text: /etc/pki/tls/certs/squid.crt
- name: private
type: filename
value:
- text: /etc/pki/tls/private/squid.key
- name: authority
type: filename
value:
- text: /etc/pki/ca-trust/source/anchors/ca_squid.crt
```
Attention, les variables doivent être de type "filename".
## Le propriétaire de la clef privée
Le certificat et le certificat de l'autorité de certification n'ont pas besoin d'être privés.
Par contre, seul le service qui doit avoir accès à la clef privée.
Par défaut seul utilisateur "root" et groupe "root" peuvent y accéder.
Il est possible de définir l'utilisateur ou le groupe de la clef privée générée :
```xml
<services>
<service name="squid">
<certificate private="/etc/pki/tls/private/squid.key" authority="/etc/pki/ca-trust/source/anchors/ca_squid.crt" owner="squid" group="squid">/etc/pki/tls/certs/squid.crt</certificate>
</service>
</services>
```
En YAML :
```yml
services:
- service:
- name: squid
certificate:
- private: /etc/pki/tls/private/squid.key
authority: /etc/pki/ca-trust/source/anchors/ca_squid.crt
owner: squid
group: squid
text: /etc/pki/tls/certs/squid.crt
```
L'utilisateur et le groupe peuvent être défini dans une variable :
```xml
<services>
<service name="squid">
<certificate private="/etc/pki/tls/private/squid.key" authority="/etc/pki/ca-trust/source/anchors/ca_squid.crt" owner="owner" owner_type="variable" group="group" group_type="variable">/etc/pki/tls/certs/squid.crt</certificate>
</service>
<variables>
<variable name="owner" type="unix_user">
<value>squid</value>
</variable>
<variable name="group" type="unix_user">
<value>squid</value>
</variable>
</services>
```
En YAML :
```yml
services:
- service:
- name: squid
certificate:
- private: /etc/pki/tls/private/squid.key
authority: /etc/pki/ca-trust/source/anchors/ca_squid.crt
owner: owner
owner_type: variable
group: group
group_type: variable
text: /etc/pki/tls/certs/squid.crt
variables:
- variable:
- name: owner
type: unix_user
value:
- text: squid
- name: group
type: unix_user
value:
- text: squid
```

View file

@ -1,454 +0,0 @@
# La gestion d'un fichier
## La balise file
La gestion des fichiers se fait dans un conteneur de [service](README.md).
La déclaration du fichier permet de générer un fichier à partir d'un template pour le déposer à l'endroit prévu dans la déclaration de cette élément.
Il est nécessaire, au minimum, de spécifier le chemin complet du fichier :
```xml
<services>
<service name="squid">
<file>/etc/squid/squid.conf</file>
</service>
</services>
```
En YAML :
```yml
services:
- service:
- name: squid
file:
- text: /etc/squid/squid.conf
```
## Le nom de template
Par défaut, le nom du template est déduit du nom du fichier.
Par xemple, si le fichier de destination est "/etc/squid/squid.conf", le template aura le nom "squid.conf".
Si le template a un nom différent (par exemple si plusieurs template se retrouve avec le même nom), il est possible de changer le nom du template avec l'attribut source :
```xml
<file source="template-squid.conf">/etc/squid/squid.conf</file>
```
En YAML :
```yml
file:
- source: template-squid.conf
text: /etc/squid/squid.conf
```
## Le nom de template dynamique
La source peut ếgalement être une variable :
```xml
<services>
<service name="squid">
<file source="source_var" source_type="variable">/etc/squid/squid.conf</file>
</service>
</services>
<variables>
<variable name="source_var">
<value>template-squid.conf</value>
</variable>
</variables>
```
En YAML :
```yml
services:
- service:
- name: squid
file:
- source: source_var
source_type: variable
text: /etc/squid/squid.conf
variables:
- variable:
- name: source_var
value:
- text: template-squid.conf
```
## Les noms de fichiers dynamique
Il est possible également de définir le nom du fichier dans une variable :
```xml
<services>
<service name="squid">
<file file_type="variable" source="squid.conf">my_variable</file>
</service>
</services>
<variables>
<variable name="my_variable">
<value>/etc/squid/squid.conf</value>
</variable>
</variables>
```
En YAML :
```yml
services:
- service:
- name: squid
file:
- file_type: variable
source: squid.conf
text: my_variable
variables:
- variable:
name: my_variable
value:
- text: /etc/squid/squid.conf
```
Attention, la variable doit être de type "filename".
Dans le cas des fichiers dynamique, la source est obligatoire.
Il est même possible de définir une variable de type multiple, ce qui génèrera plusiers fichiers :
```xml
<services>
<service name="squid">
<file file_type="variable" source="squid.conf">my_variable</file>
</service>
</services>
<variables>
<variable name="my_variable" multi="True">
<value>/etc/squid1/squid.conf</value>
<value>/etc/squid2/squid.conf</value>
</variable>
</variables>
```
En YAML :
```yml
services:
- service:
- name: squid
file:
- file_type: variable
source: squid.conf
text: my_variable
variables:
- variable:
name: my_variable
multi: true
value:
- text: /etc/squid1/squid.conf
- text: /etc/squid2/squid.conf
```
Dans ce cas là, le fichier source est identique mais les fichiers de destination seront différent.
Il peut être important de personnaliser le contenu du fichier suivant le fichier de destination.
Dans ce cas il y a deux possibilités :
- la variable "rougail_filename" contient le nom de fichier de destination
- l'utilisateur de l'attribut "variable"
En effet, il est possible de passer le contenu d'une variable au template :
```xml
<services>
<service name="squid">
<file file_type="variable" source="squid.conf" variable="my_variable2">my_variable1</file>
</service>
</services>
<variables>
<variable name="my_variable1" multi="True">
<value>/etc/squid1/squid.conf</value>
<value>/etc/squid2/squid.conf</value>
</variable>
<variable name="my_variable2" multi="True">
<value>squid1</value>
<value>squid2</value>
</variable>
</variables>
```
En YAML :
```yml
services:
- service:
- name: squid
file:
- file_type: variable
source: squid.conf
variable: my_variable2
text: my_variable1
variables:
- variable:
name: my_variable1
multi: true
value:
- text: /etc/squid1/squid.conf
- text: /etc/squid2/squid.conf
- variable:
name: my_variable2
multi: true
value:
- text: squid1
- text: squid2
```
Dans ce cas, lors de la génération du fichier /etc/squid1/squid.conf on retrouvera la variable "rougail_variable" avec la valeur "squid1" et la variable "rougail_index" avec la valeur "0". Lors de la génération du fichier /etc/squid2/squid.conf on retrouvera la variable "rougail_variable" avec la valeur "squid2" et la variable "rougail_index" avec la valeur "1".
Attention : les deux variables "my_variable1" et "my_variable2" doivent être multiple et de même longueur.
## Les droits des fichiers
Par défaut les droits du fichier généré sont "0644" avec comme utilisateur "root" et groupe "root".
Il est possible de définir les droits, l'utilisateur ou le groupe d'un fichier généré :
```xml
<file mode="0640" owner="nobody" group="squid">/etc/squid/squid.conf</file>
```
En YAML :
```yml
file:
- mode: '0640'
owner: nobody
group: squid
text: /etc/squid/squid.conf
```
Il est possible de personnaliser les droits par défaut dans la [configuration de rougail](../dev/config.md)
## Désactiver la génération d'un fichier
Il est possible de désactiver la génération d'un fichier avec l'attribut "disabled" :
```xml
<file disabled="True">/etc/squid/squid.conf</file>
```
En YAML :
```yml
file:
- disabled: true
text: /etc/squid/squid.conf
```
Il est aussi possible de définir une [condition](../condition/README.md) de type "disabled_if_in" ou "disabled_if_not_in" sur une balise fichier :
```xml
<services>
<service name="test">
<file filelist="squid">/etc/squid/squid.conf</file>
</service>
</services>
<variables>
<variable name="condition" type="boolean"/>
</variables>
<constraints>
<condition name="disabled_if_in" source="condition">
<param>False</param>
<target type="filelist">squid</target>
</condition>
</constraints>
```
En YAML :
```yml
services:
- service:
- name: text
file:
- filelist: squid
text: /etc/squid/squid.conf
variables:
- variable:
name: condition
type: boolean
constraints:
- condition:
- name: disabled_if_in
source: condition
param:
- text: false
target:
- type: filelist
text: squid
```
Dans ce cas, tous les fichiers avec un attribut filelist à "squid" seront désactivés si la variable "condition" est False.
## Redéfinir une fichier
Il est possible de redéfinir les éléments d'un fichier dans un dictionnaire différent en utilisant l'attribut redefine :
```xml
<file source="template-squid.conf" redefine="True">/etc/squid/squid.conf</file>
```
En YAML :
```yml
file:
- source: template-squid.conf
redefine: true
text: /etc/squid/squid.conf
```
## Choix du moteur de templating
Par défaut, le moteur de templating est le moteur de templating compatible avec "cheetah".
Il est possible de désactiver la templatisation du fichier (il sera alors uniquement copié) :
```xml
<file engine="none">/etc/squid/squid.conf</file>
```
En YAML :
```yml
file:
- engine: 'none'
text: /etc/squid/squid.conf
```
Ou d'utiliser le moteur "jinja" :
```xml
<file engine="jinja">/etc/squid/squid.conf</file>
```
En YAML :
```yml
file:
- engine: jinja
text: /etc/squid/squid.conf
```
Il est possible de personnaliser le moteur par défaut dans la [configuration de rougail](../dev/config.md)
## Inclusion de template
Un attribut "included" permet de définir la nature du fichier. Cet attribut peut avoir trois valeurs :
- "no" : c'est un fichier normal
- "name" : le répertoire de destination est listé dans un autre template, il faut que le fichier soit généré avant cet autre template
- "content" : le contenu du fichier est copié dans un autre template, il faut que le fichier soit généré avant cet autre template et ce fichier n'a pas besoin d'être installé sur le serveur cible.
Bien entendu, c'est au développeur de lister ou d'inclure le contenu de ce template dans le fichier de destination. Cet attribut permet juste de garantir que le fichier sera fait avant l'autre et de ne pas l'installer sur le serveur si ce n'est pas nécessaire.
Il est possible de personnaliser les methodes d'inclusion par défaut dans la [configuration de rougail](../dev/config.md)
Exemples :
- créons un template qui inclut des noms de fichiers :
Le contenu de ce template (squid.conf) est :
```
%import os
%set %%confdir = 'etc/squid/squid.d/'
%if %%os.path.exists(%%confdir)
%set %%files = %%os.listdir(%%confdir)
%%files.sort()
%for %%file in %%files
%if %%file.endswith('.cfg')
include '/' + %%confdir + file
%end if
%end for
%end if
```
Ajoutons un second template (squid.included.conf) qui sera copié dans ce répertoire :
```
template content
```
Et déclaront ces deux templates :
```xml
<file>/etc/squid/squid.conf</file>
<file included="name" engine="none">/etc/squid/squid.d/squid.conf</file>
```
En YAML :
```yml
file:
- text: /etc/squid/squid.conf
- included: name
engine: 'none'
text: /etc/squid/squid.d/squid.conf
```
Le contenu du fichier généré (/etc/squid/squid.conf) sera donc :
```
include squid.d/squid.conf
```
- créons un template qui inclut le contenu de fichiers :
Le contenu de ce template (squid.conf) est :
```
%import os
%set %%confdir = 'squid.d/'
%if %%os.path.exists(%%confdir)
%set %%files = %%os.listdir(%%confdir)
%%files.sort()
%for %%file in %%files
%if %%file.endswith('.cfg')
%include raw %%confdir + file
%end if
%end for
%end if
```
Ajoutons un second template (squid.included.conf) qui sera copié dans ce répertoire :
```
template content
```
Et déclaront ces deux templates :
```xml
<file>/etc/squid/squid.conf</file>
<file included="content" engine="none">squid.d/squid.conf</file>
```
En YAML :
```yml
file:
- text: /etc/squid/squid.conf
- included: content
engine: 'none'
text: squid.d/squid.conf
```
Le contenu du fichier généré (/etc/squid/squid.conf) sera donc maintenant :
```
template content
```

View file

@ -1,91 +0,0 @@
# La gestion d'une IP
## La balise IP
La gestion des IP se fait dans un conteneur de [service](README.md).
La déclaration de l'attribut permet d'associer une IP autorisé à accéder au service.
Il est nécessaire, au minimum, de spécifier le nom d'une variable de type "IP" :
```xml
<ip ip_type="variable">variable_ip</ip>
```
En YAML :
```yml
ip:
- ip_type: variable_ip
text: variable_ip
```
## La gestion d'un réseau
L'adresse peut être de type réseau ("network") :
```xml
<ip netmask="variable_netmask">variable_ip</ip>
```
En YAML :
```yml
ip:
- netmask: variable_netmask
text: variable_ip
```
Attention, dans ce cas il faut préciser une variable de type "netmask" dans l'attribut netmask.
## Désactiver la génération d'une IP
Il est possible de définir une [condition](../condition/README.md) de type "disabled_if_in" ou "disabled_if_not_in" sur une balise IP :
```xml
<services>
<service name="test">
<ip iplist="test_ip">variable_ip</ip>
</service>
</services>
<variables>
<variable name="condition" type="boolean"/>
<variable name="variable_ip" type="ip"/>
</variables>
<constraints>
<condition name="disabled_if_in" source="condition">
<param>False</param>
<target type="iplist">test_ip</target>
</condition>
</constraints>
```
En YAML :
```yml
services:
- service:
- name: test
ip:
- iplist: test_ip
text: variable_ip
variables:
- variable:
name: condition
type: boolean
- variable:
name: variable_ip
type: ip
constraints:
- condition:
- name: disabled_if_in
source: condition
param:
- text: false
target:
- type: iplist
text: test_ip
```
Dans ce cas, tous les IP avec un attribut iplist à "test_ip" seront désactivé si la variable "condition" est False.

View file

@ -1,75 +0,0 @@
# Réécriture du service
## La balise override
La gestion des overrides se fait dans un conteneur de [service](README.md).
La balise override permet de redéfinir facilement un service systemd.
Il suffit d'avoir un template dont le nom est par défaut le nom du service avec l'extension "service" et de déclarer la balise :
```xml
<services>
<service name="squid">
<override/>
</service>
</services>
```
En YAML :
```yml
services:
- service:
- name: squid
override: null
```
Dans cette exemple, le template associé doit s'appeler squid.service
Si le fichier service a un nom différent (par exemple si plusieurs template se retrouve avec le même nom), il est possible de changer le nom du template avec l'attribut source :
```xml
<override source="test.service"/>
```
En YAML :
```yml
override:
- source: test.service
```
Dans ce cas le fichier de destination aura le même nom.
## Choix du moteur de templating
Par défaut, le moteur de templating est le moteur de templating compatible avec "cheetah".
Il est possible de désactiver la templatisation du fichier (il sera alors uniquement copié) :
```xml
<override engine="none"/>
```
En YAML :
```yml
override:
- engine: 'none'
```
Ou d'utiliser le moteur "jinja" :
```xml
<override engine="jinja"/>
```
En YAML :
```yml
override:
- engine: 'jinja'
```
Il est possible de personnaliser le moteur par défaut dans la [configuration de rougail](../dev/config.md)

View file

@ -1,21 +0,0 @@
# Le conteneur des services
La balise "services" est le conteneur de l'ensemble des [services](service/README.md).
Il est placé à la racine du dictionnaire :
```xml
<?xml version='1.0' encoding='UTF-8'?>
<rougail>
<services/>
</rougail>
```
En YAML :
```yml
version: '0.10'
services: none
```
Attention, cette balise ne peut pas être placé dans un dictionnaire "extra".

View file

@ -1,5 +0,0 @@
# La cible
- [De type variable](../target/variable.md)
- [De type famille](../target/family.md)
- [De type \*list](../target/list.md)

View file

@ -1,30 +0,0 @@
# Cible de type "variable"
Une cible peut être de type famille :
```xml
<target type="family">my_family</target>
```
En YAML :
```yml
target:
- type: family
text: my_family
```
Mais une target peut être optionnelle. C'est à dire que si la famille n'existe pas, l'action ne sera pas associé à cette famille.
```xml
<target type="family" optional="True">my_family</target>
```
En YAML :
```yml
target:
- type: family
optional: true
text: my_family
```

View file

@ -1,68 +0,0 @@
# Cible de type \*list
## Les différences cible de type \*.list
### servicelist
Une cible peut être de type [service](../service/README.md) :
```xml
<target type="servicelist">example</target>
```
En YAML :
```yml
target:
- type: servicelist
text: example
```
### filelist
Une cible peut être de type [fichier](../service/file.md) :
```xml
<target type="filelist">example</target>
```
En YAML :
```yml
target:
- type: filelist
text: example
```
### iplist
Une cible peut être de type [ip](../service/ip.md) :
```xml
<target type="iplist">example</target>
```
En YAML :
```yml
target:
- type: iplist
text: example
```
## La cible optionnelle
Mais une target peut être optionnelle. C'est à dire que si la \*list n'existe pas, l'action ne sera pas associé.
```xml
<target type="filelist" optional="True">unknown</target>
```
En YAML :
```yml
target:
- type: filelist
optional: true
text: unknown
```

View file

@ -1,28 +0,0 @@
# Cible de type "variable"
Par défaut une cible est de type variable.
```xml
<target>my_variable</target>
```
En YAML :
```yml
target:
- text: my_variable
```
Mais une target peut être optionnelle. C'est à dire que si la variable n'existe pas, l'action ne sera pas associé à cette variable.
```xml
<target optional='True'>my_variable</target>
```
En YAML :
```yml
target:
- optional: true
text: my_variable
```

103
doc/template/README.md vendored
View file

@ -1,103 +0,0 @@
# Les templates
## Le moteur "cheetah"
Le moteur de templating par défaut est le moteur [Cheetah](https://cheetahtemplate.org/).
Par contre, la configuration par défaut de Cheetah a été modifié.
Dans un template de configuration, il est très fréquent que le caractère "#" est le caractère des commentaires.
C'est pourquoi la configuration par défaut a été modifié.
Les choix sont maintenant les suivants :
- le caractère des directives : "%" ;
- les variables : "%%" ;
- le caractère des commentaires : "#".
Voici quelques exemples d'utilisateurs de ce moteur :
### utiliser une variable
```
%%variable_name
```
### condition
```
%if %%variable_name == 'oui'
text
%end if
```
### vérifier si une variable existe
```
%if %%varExists('variable_name')
text
%end if
```
### boucle
```
%for %%var in %%variable_name
%%var
%end for
```
### boucle avec variables meneuse et suiveuse
```
%for %%var in %%variable_leader
%%var.variable_follower
%end for
```
Pour plus d'informations, voir la documentation de Cheetah.
## Le moteur "jinja"
Il est possible d'utiliser le moteur de templating [Jinja](https://jinja.palletsprojects.com/).
Il n'y a pas d'adaptation particulière pour ce moteur.
Voici quelques exemples d'utilisateurs de ce moteur :
### utiliser une variable
```
{{ variable_name }}
```
### condition
```
{% if variable_name == 'oui' %}
text
{% endif -%}
```
### boucle
```
{% for var in variable_name %}
{{ var }}
{% endfor -%}
```
### boucle avec variables meneuse et suiveuse
```
{% for var in variable_leader %}
{{ var.variable_follower }}
{% endfor -%}
```
Pour plus d'informations, voir la documentation de Jinja.
## Le moteur "none"
Ce moteur permet de copie le fichier sans y apporter la moindre modification.
C'est utile pour les templates ne contenant aucune variable ni condition.

39
doc/template/patch.md vendored
View file

@ -1,39 +0,0 @@
# Patcher un template
Il peut être intéressant de réaliser un patch à un template pour corriger un problème spécifique à notre environnement, sans attendre que le mainteneur du template n'est fait la correction.
Par exemple le template :
```
The value: %%my_value
The extra value: %%example.my_variable_extra
```
Peut être modifié via le patch :
```patch
--- tmpl/example.conf 2021-02-13 19:41:38.677491087 +0100
+++ tmp/example.conf 2021-02-13 20:12:55.525089820 +0100
@@ -1,3 +1,5 @@
The value: %%my_variable
The extra value: %%example.my_variable_extra
+
+Add by a patch
```
Le fichier généré ressemblera alors à cela :
```
The value: my_value
The extra value: my_value_extra
Add by a patch
```
Deux choses importantes à savoir sur les patchs :
- le nom du patch est obligatoire le nom du template source + ".patch"
- la deuxième ligne doit toujours commencer par "+++ tmp/" (tmp étant le nom du répertoire mis dans la configuration) + le nom du template source

View file

@ -1,480 +1,247 @@
---
gitea: none
include_toc: true
---
# Variable
## Un variable
## Synopsis
Une variable est forcement dans [variables](../variables.md) ou dans une [famille](../family/README.md).
Une variable est un conteneur qui contiendra une ou plusieurs valeurs.
Une variable est déjà un nom. C'est à dire qu'on pourra utiliser plus tard la variable via ce nom.
## Paramètres
```xml
<variables>
<variable name="my_variable"/>
<family name="my_family">
<variable name="my_family_variable"/>
</variable>
</variables>
```
| 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. |
En YAML :
## 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
variables:
- variable:
name: my_variable
- family:
name: my_family
variables:
- variable:
name: my_family_variable
---
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
```
## Description et aide sur la variable
En plus d'un nom, il est possible de mettre une "description" à la variable. C'est une information "utilisateur" qui nous permettra d'avoir des informations complémentaires sur le contenu de cette variable :
```xml
<variable name="my_variable" description="This is a good variable"/>
```
En YAML :
```yml
variables:
- variable:
name: my_variable
description: This is a good variable
```
En plus de la description, il est possible de préciser une aide complémentaire :
```xml
<variable name="my_variable" help="This is a good variable"/>
```
En YAML :
```yml
variables:
- variable:
name: my_variable
help: This is a good variable
```
Cette aide peut être utilisé à tout moment comme valeur [d'un paramètre](../param/information.md).
## Le type de la variable
Une variable a un type. Ce type permet de définir les valeurs acceptées par cette variable :
- string : chaine de caractère (type par défaut)
- number : un nombre
- float : un chiffre flottant
- boolean : "True" ou "False", si aucune valeur n'est défini la valeur par défaut de cette variable sera "True", ces variables sont également obligatoire par défaut
- secret (ou password mais est obsolète) : un secret (comme un mot de passe, une clef privée, ...)
- mail : une adresse mail
- filename : nom de fichier au sens Unix (exemple : "/etc/passwd")
- date : une date au format "%Y-%m-%d" (exemple : "2021-01-30")
- unix_user : nom d'utilisateur au sens Unix
- ip : n'importe quelle adresse IPv4
- cidr : n'importe quelle adresse IPv4 au format CIDR
- local_ip : adresse IPv4 sur un réseau local, si l'adresse IPv4 n'est pas local, un warning sera afficher mais la valeur sera accepté tout de même
- netmask : masque d'une adresse IPv4
- network : adresse réseau
- network_cidr : adresse réseau au format CIDR
- broadcast : adresse de diffusion
- netbios : nom netbios
- domain : nom de domaine
- hostname : nom d'hôte
- web_address : adresse web (http://www.silique.fr/)
- port : port
- mac : adresse MAC
- schedule : périodicité du schedule, les valeurs possibles sont "none", "daily", "weekly" ou "monthly"
- schedulemod : type de schedule, les valeurs possibles sont "pre" ou "post"
Pour définir le type d'une variable :
```xml
<variable name="my_variable" type="number"/>
```
En YAML :
```yml
- variable:
name: my_variable
type: number
```
## Variable à valeur multiple
Par défaut une variable ne peut acceuillir qu'une seule valeur. Il peut être utile de pouvoir spécifier plusieurs valeurs à une même variable.
Pour définir une variable à valeur multiple :
```xml
<variable name="my_variable" multi="True"/>
```
En YAML :
```yml
- variable:
name: my_variable
multi: true
```
Par défaut, les variables multiples ne peuvent pas avoir deux fois la même valeur.
Si vous voulez pouvoir mettre plusieurs fois la même valeur, il faut utiliser l'attribut "unique" :
```xml
<variable name="my_variable" multi="True" unique="False"/>
```
En YAML :
```yml
- variable:
name: my_variable
multi: true
unique: false
```
## Variable invisible
Il est possible de cacher une variable.
Cacher une variable signifie qu'elle ne sera pas visible lorsqu'on modifie la configuration du service.
Par contre cette variable sera accessibles lorsqu'on va l'utiliser.
Pour cacher une variable :
```xml
<variable name="my_variable" hidden="True"/>
```
En YAML :
```yml
- variable:
name: my_variable
hidden: true
```
## Variable désactive
Il est possible de désactiver une variable.
Désactiver une variable signifie qu'elle ne sera pas visible lorsqu'on modifie la configuration du service mais également lorsqu'on va l'utiliser.
Pour désactiver une variable :
```xml
<variable name="my_variable" disabled="True"/>
```
En YAML :
```yml
- variable:
name: my_variable
disabled: true
```
## Variable obligatoire
Variable dont une valeur est requise :
```xml
<variable name="my_variable" mandatory="True"/>
```
En YAML :
```yml
- variable:
name: my_variable
mandatory: true
```
Les variables booléans sont par défaut obligatoire. Pour qu'une variable booléan ne soit pas obligatoire il faut le préciser explicitement :
```xml
<variable name="my_variable" type="boolean" mandatory="False"/>
```
En YAML :
```yml
- variable:
name: my_variable
type: boolean
mandatory: false
```
Les variables avec une valeur par défaut (non calculée) sont également automatiquement obligatoire.
## Valeur par défaut d'une variable
Il est possible de fixer les valeurs par défaut d'une variable :
```xml
<variable name="my_variable">
<value>value</value>
</variable>
```
En YAML :
```yml
- variable:
name: my_variable
value:
- text: value
```
Pour une variable multiple, il est possible de préciser plusieurs valeurs :
```xml
<variable name="my_variable" multi="True">
<value>value 1</value>
<value>value 2</value>
</variable>
```
En YAML :
```yml
- variable:
name: my_variable
multi: true
value:
- text: value 1
- text: value 2
```
Si la variable n'est pas pas une [variable meneuse](../family/leadership.md), la première valeur défini dans cette liste sera également la valeur par défaut proposé si on ajoute une nouvelle valeur à cette variable.
Une valeur par défaut peut également être [une valeur calculer](../fill/README.md).
## Redéfinir une variable
Il est possible de définir une variable dans un dictionnaire et de changer son comportement dans une second dictionnaire.
Attention trois attributs ne sont redéfinisable :
- name
- type
- multi
### Redéfinition d'une variable
Créons notre variable :
```xml
<variable name="my_variable"/>
```
En YAML :
```yml
- variable:
name: my_variable
---
version: '1.0'
my_variable:
type: string
```
Et redéfinisons là :
```xml
<variable name="my_variable" redefine="True" description="New description"/>
```yml
---
version: '1.0'
my_variable:
redefine: true
description: New description
```
En YAML :
### Créer une variable inexistante
```yml
- variable:
name: my_variable
redefine: true
description: New description
```
## Créer une variable inexistante
Il est parfois utile de créer une variable si elle n'existe pas dans un autre dictionnaire :
```xml
<variable name="my_variable" exists="False"/>
```
En YAML :
```yml
- variable:
name: my_variable
---
version: '1.0'
my_variable:
exists: false
```
Si cette variable existe dans un autre dictionnaire, elle ne sera pas modifié ni recréé
## 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à :
```xml
<variable name="my_variable" redefine="True" exists="True" hidden="True"/>
```yml
---
version: '1.0'
my_variable:
exists: true
hidden: true
```
En YAML :
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
- variable:
name: my_variable
exists: true
hidden: true
---
version: '1.0'
my_variable:
type: choice
choices:
- val1
- val2
- val3
```
## Variable à valeur automatiquement modifiée
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".
Une variable avec valeur automatiquement modifiée est une variable dont la valeur sera considéré comme modifié quand la propriété global "force_store_value" de Tiramisu est mise.
Voici une variable a valeur automatiquement modifiée :
```xml
<variable name="my_variable" auto_save="True">
<value>my_value</value>
</variable>
```
En YAML :
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
- variable:
name: my_variable
auto_save: true
value:
- text: my_value
---
version: '1.0'
my_variable:
type: choice
choices:
- val1
- val2
- val3
default: val1
```
Dans ce cas la valeur est fixée à la valeur actuelle.
Par exemple, si la valeur de cette variable est issue d'un calcul, la valeur ne sera plus recalculée.
Une variable à choix est typée :
Ces variables sont généralement des variables obligatoires. En effet ces variable ne sont automatiquement modifiées que si elles ont une valeurs.
Une [variable meneuse ou suiveuse](../family/leadership.md) ne peut pas avoir la propriété auto_save.
## Variable à valeur en lecture seule automatique
Une variable avec valeur en lecture seule automatique est une variable dont la valeur ne sera plus modifiable par l'utilisateur quand la [variable "server_deployed" passe à "True"](../dev/config.md).
Voici un variable à valeur en lecture seule automatique :
```xml
<variable name="server_deployed" type="boolean">
<value>False</value>
</variable>
<variable name="my_variable" auto_freeze="True"/>
```
En YAML :
Les choix sont typés :
```yml
- variable:
name: server_deployed
type: boolean
value:
- text: 'False
- variable:
name: my_variable
auto_freeze: true
---
version: '1.0'
my_variable:
type: choice
choices:
- val1
- 3
- true
default: val1
```
Dans ce cas la valeur est fixée à la valeur actuelle et elle ne sera plus modifiable par l'utilisateur.
Par exemple, si la valeur de cette variable est issue d'un calcul, la valeur ne sera plus recalculée.
dans ce cas, le chiffre 3 est autorisé mais pas la chaine de caractère "3".
Ces variables sont généralement des variables obligatoires. En effet ces variable ne sont en lecteur seul que si elles ont une valeurs.
## Un variable à choix provenant de variable
Une [variable meneuse ou suiveuse](../family/leadership.md) ne peut pas avoir la propriété auto_freeze.
## Information "test"
L'attribut "test" est un attribut spécial qui permet aux concepteurs d'un dictionnaire d'influancer le robot de test en précisant de valeurs utile à tester.
Concrêtement, le contenu de cet attribut est enregister dans une "information" de l'option Tiramisu correspondante.
Exemple :
```xml
<variable name="my_variable" test="yes"/>
```
En YAML :
Les choix d'une variable peuvent provenir d'une autre variable multiple :
```yml
- variable:
name: my_variable
test: yes
---
version: '1.0'
source_variable:
multi: true
default:
- val1
- val2
my_variable:
type: choice
choices:
type: variable
variable: rougail.source_variable
```
Il est possible de préciser plusieurs valeurs avec le séparateur "|" :
Dans ce cas, toutes les valeurs de la variable ou des variables seront des choix utilisables par l'utilisateur.
```xml
<variable name="my_variable" test="yes|no"/>
```
## Un variable à choix provenant d'un template Jinja
En YAML :
```yml
- variable:
name: my_variable
test: yes|no
```
Cette valeur peut être utilisé à tout moment comme valeur [d'un paramètre](../param/information.md).
## Mode de la variable
Le [mode](../mode.md) par défaut d'une variable correspond au [mode](../mode.md) de la [famille](../family/README.md).
Cas particuliers :
- une variable à valeur automatiquement modifiée ou une variable en lecture seule automatique est par défaut en mode "basic".
- si la variable n'est pas dans une famille, la variable aura le mode "normal" par défaut.
- une variable obligatoire sans valeur par défaut (calculer ou non) aura le mode "basic".
Pour définir le [mode](../mode.md) :
```xml
<variable name="my_variable" mode="expert"/>
```
En YAML :
```yml
- variable:
name: my_variable
mode: expert
```
## Les variables qui fournissent des valeurs
Il peut être intéressant de retrouver facilement des variables sans connaitre le chemin complet mais en utilisant le contenu du paramètre "provider".
C'est particulièrement utile si un service peut être fournit par plusieurs services. Les variables n'auront donc pas le même nom. Utiliser ce paramètre, permet donc de retrouver facilement la variable.
Pour déclarer :
```xml
<variable name="my_variable" provider="my_function"/>
```
En YAML :
```yml
- variable:
name: my_variable
provider: my_variable
```
Dans le code de l'application, on pourra retrouver le chemin de la variable en faisant :
Faisons d'abord une fonction `trange` dans le fichier functions.py :
```python
print(await config.information.get('provider:my_function'))
def trange(min, max):
return range(min, max)
```
Pour les variables inclusent dans une famille dynamique, le chemin de la variable sera un template comme ceci "rougail.family_{suffix}.my_variable_{suffix}". Il vous suffit de remplacer "{suffix}" par le suffix voulu de la famille dynamique.
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
```

View file

@ -1,186 +0,0 @@
# Les variables à choix
## Une variable à choix
Il est possible d'imposer une liste de valeur pour une variable particulière :
```xml
<variable name="my_variable" type="choice">
<choice>val1</choice>
<choice>val2</choice>
<choice>val3</choice>
</variable>
```
En YAML :
```yml
- variable:
name: my_variable
type: choice
choice:
- text: val1
- text: val2
- text: val3
```
Dans ce cas, seules les valeurs proposées sont possibles pour cette variable.
Cette variable n'est pas obligatoire dont 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é :
```xml
<variable name="my_variable" type="choice">
<choice>val1</choice>
<choice>val2</choice>
<choice>val3</choice>
<value>val1</value>
</variable>
```
En YAML :
```yml
- variable:
name: my_variable
type: choice
choice:
- text: val1
- text: val2
- text: val3
value:
- text: val1
```
## Un variable à choix typée
Par défaut les choix sont de type "string". Il est possible de préciser des nombres, des booléens ou la valeur None :
```xml
<variable name="my_variable" type="choice">
<choice>val1</choice>
<choice type="string">val2</choice>
<choice type="number">3</choice>
<choice type="boolean">True</choice>
<choice type="nil"/>
</variable>
```
En YAML :
```yml
- variable:
name: my_variable
type: choice
choice:
- text: val1
- type: string
text: val2
- type: number
text: 3
- type: boolean
text: true
- type: 'nil'
```
Comme vu précédement ajouter la valeur None n'est pas utile parce qu'elle est automatiquement ajouté si la variable n'est pas obligatoire.
## Ajouter une option à une variable à choix existante
Pour ajouter un choix à une variable à choix existante, rien de plus simple, juste redéfinir la variable en ajoutant le choix voulu :
```xml
<variable name="my_variable" redefine="True">
<choice>val4</choice>
</variable>
```
En YAML :
```yml
- variable:
name: my_variable
redefine: true
choice:
- text: val4
```
## Redéfinir une option à choix
Si on veut supprimer un choix ou redéfinir complètement la liste, il faut redéfinir cette variable et ajouter l'attribut "remove_choice" à "True" :
```xml
<variable name="my_variable" redefine="True" remove_choice="True">
<choice>val1</choice>
<choice>val2</choice>
</variable>
```
En YAML :
```yml
- variable:
name: my_variable
redefine: true
remove_choice: true
choice:
- text: val1
- text: val2
```
Dans ce cas toutes les anciens choix ne seront plus possible. Seuls les nouveaux le seront.
## Un variable à choix provenant d'une variable
Une variable à valeur multiple peut servir de source des choix :
```xml
<variable name="my_variable" type="choice">
<choice type="variable">other_variable</choice>
</variable>
```
En YAML :
```yml
- variable:
name: my_variable
type: choice
choice:
- type: variable
text: other_variable
```
Dans ce cas, toutes les valeurs de la variable seront des choix utilisables par l'utilisateur.
Seul un choice de type "variable" est possible par variable.
## Un variable à choix provenant d'une fonction
```xml
<variable name="my_variable" type="choice">
<choice type="function" name="range">
<param type="number">0</param>
<param type="number">10</param>
</choice>
<value type="number">9</value>
</variable>
```
En YAML :
```yml
- variable:
name: my_variable
type: choice
choice:
- type: function
name: range
param:
- type: number
text: 0
- type: number
text: 10
value:
- type: number
text: 9
```

View file

@ -1,19 +0,0 @@
# Le conteneur des variables
La balise "variables" est le conteneur de l'ensemble des [familles](family/README.md) et des [variables](variable/README.md).
Il est placé à la racine du dictionnaire :
```xml
<?xml version='1.0' encoding='UTF-8'?>
<rougail>
<variables/>
</rougail>
```
En YAML :
```yml
version: '0.10'
variables: none
```

View file

@ -28,8 +28,6 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from .convert import RougailConvert
from .template.base import RougailBaseTemplate
from .template.systemd import RougailSystemdTemplate
from .config import RougailConfig
from tiramisu import Config
from .update import RougailUpgrade
@ -45,22 +43,18 @@ class Rougail:
self.converted = RougailConvert(self.rougailconfig)
self.config = None
def add_path_prefix(self,
path_prefix: str,
) -> None:
self.converted.parse_directories(path_prefix)
def get_config(self):
if not self.config:
tiram_obj = self.converted.save(None)
tiram_obj = self.converted.save(self.rougailconfig['tiramisu_cache'])
optiondescription = {}
exec(tiram_obj, None, optiondescription)
self.config = Config(optiondescription['option_0'])
self.config.property.read_write()
return self.config
def template(self,
type: str='base',
) -> None:
config = self.get_config()
if type == 'base':
engine = RougailBaseTemplate(config, self.rougailconfig)
else:
engine = RougailSystemdTemplate(config, self.rougailconfig)
engine.instance_files()
__ALL__ = ('Rougail', 'RougailConvert', 'RougailBaseTemplate', 'RougailSystemdTemplate', 'RougailConfig', 'RougailUpgrade')
__ALL__ = ('Rougail', 'RougailConvert', 'RougailConfig', 'RougailUpgrade')

View file

@ -29,25 +29,25 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from .variable import CONVERT_OPTION
import importlib.resources
from os.path import dirname, join, isfile
from os.path import isfile
from ..utils import load_modules
ANNOTATORS = None
if not 'files' in dir(importlib.resources):
# old python version
class fake_files:
def __init__(self, package):
self.mod = []
dir_package = dirname(importlib.resources._get_package(package).__file__)
for mod in importlib.resources.contents(package):
self.mod.append(join(dir_package, mod))
def iterdir(self):
return self.mod
importlib.resources.files = fake_files
#
#
#if not 'files' in dir(importlib.resources):
# # old python version
# class fake_files:
# def __init__(self, package):
# self.mod = []
# dir_package = dirname(importlib.resources._get_package(package).__file__)
# for mod in importlib.resources.contents(package):
# self.mod.append(join(dir_package, mod))
#
# def iterdir(self):
# return self.mod
# importlib.resources.files = fake_files
def get_level(module):
@ -58,7 +58,7 @@ def get_annotators(annotators, module_name):
annotators[module_name] = []
for pathobj in importlib.resources.files(module_name).iterdir():
path = str(pathobj)
if path.endswith('__') or path.endswith('__.py'):
if not path.endswith('.py') or path.endswith('__.py'):
continue
module = load_modules(path)
if 'Annotator' not in dir(module):
@ -84,17 +84,20 @@ class SpaceAnnotator: # pylint: disable=R0903
for extra_annotator in objectspace.rougailconfig['extra_annotators']:
annotators.extend(ANNOTATORS[extra_annotator])
annotators = sorted(annotators, key=get_level)
functions = []
functions = {}
functions_files = objectspace.rougailconfig['functions_file']
if not isinstance(functions_files, list):
functions_files = [functions_files]
for functions_file in functions_files:
if isfile(functions_file):
functions.extend(dir(load_modules(functions_file)))
loaded_modules = load_modules(functions_file)
for function in dir(loaded_modules):
if function.startswith('_'):
continue
functions[function] = getattr(loaded_modules, function)
objectspace.functions = functions
for annotator in annotators:
annotator(objectspace,
functions,
)
annotator(objectspace)
__all__ = ('SpaceAnnotator', 'CONVERT_OPTION')

View file

@ -32,7 +32,7 @@ from copy import copy
from rougail.annotator.target import TargetAnnotator
from rougail.annotator.param import ParamAnnotator
from rougail.annotator.fill import get_jinja_variable_to_param
#from rougail.annotator.fill import get_jinja_variable_to_param
from rougail.i18n import _
from rougail.error import DictConsistencyError, display_xmlfiles
@ -45,9 +45,9 @@ class Annotator(TargetAnnotator, ParamAnnotator):
level = 40
def __init__(self,
objectspace,
functions,
*args,
):
return
self.objectspace = objectspace
self.only_variable = True
self.target_is_uniq = False

View file

@ -52,24 +52,22 @@ class Annotator(TargetAnnotator, ParamAnnotator, Walk):
*args,
):
self.objectspace = objectspace
self.target_is_uniq = False
self.only_variable = False
self.allow_function = False
self.force_service_value = {}
if hasattr(objectspace.space, 'variables'):
self.convert_auto_freeze()
for path_prefix, constraints in self.get_constraints():
if not hasattr(constraints, 'condition'):
continue
self.convert_target(constraints.condition, path_prefix)
self.check_condition_optional(constraints, path_prefix)
self.convert_condition_source(constraints, path_prefix)
self.convert_param(constraints.condition, path_prefix)
self.check_source_target(constraints)
self.convert_xxxlist(constraints, path_prefix)
self.check_choice_option_condition(constraints, path_prefix)
self.remove_condition_with_empty_target(constraints)
self.convert_condition(constraints, path_prefix)
# self.target_is_uniq = False
# self.only_variable = False
# self.allow_function = False
# self.force_service_value = {}
#for path_prefix, constraints in self.get_constraints():
# if not hasattr(constraints, 'condition'):
# continue
# self.convert_target(constraints.condition, path_prefix)
# self.check_condition_optional(constraints, path_prefix)
# self.convert_condition_source(constraints, path_prefix)
# self.convert_param(constraints.condition, path_prefix)
# self.check_source_target(constraints)
# self.convert_xxxlist(constraints, path_prefix)
# self.check_choice_option_condition(constraints, path_prefix)
# self.remove_condition_with_empty_target(constraints)
# self.convert_condition(constraints, path_prefix)
def valid_type_validation(self,
obj,
@ -78,47 +76,6 @@ class Annotator(TargetAnnotator, ParamAnnotator, Walk):
return None
return obj.source.type
def convert_auto_freeze(self):
"""convert auto_freeze
only if auto_freeze_variable is True this variable is frozen
"""
for variable in self.get_variables():
if not variable.auto_freeze and not variable.auto_save:
continue
#if variable.namespace != self.objectspace.rougailconfig['variable_namespace']:
# msg = _(f'auto_freeze is not allowed in extra "{variable.namespace}"')
# raise DictConsistencyError(msg, 49, variable.xmlfiles)
variable.force_store_value = True
if variable.auto_save:
continue
auto_freeze_variable = self.objectspace.rougailconfig['auto_freeze_variable']
if not self.objectspace.paths.path_is_defined(auto_freeze_variable,
self.objectspace.rougailconfig['variable_namespace'],
variable.path_prefix,
):
msg = _(f'the variable "{variable.name}" is auto_freeze but there is no variable "{auto_freeze_variable}"')
raise DictConsistencyError(msg, 81, variable.xmlfiles)
new_condition = self.objectspace.condition(variable.xmlfiles)
new_condition.name = 'auto_frozen_if_in'
new_condition.namespace = variable.namespace
new_condition.source = auto_freeze_variable
new_param = self.objectspace.param(variable.xmlfiles)
new_param.text = True
new_condition.param = [new_param]
new_target = self.objectspace.target(variable.xmlfiles)
new_target.type = 'variable'
path = variable.path
if variable.path_prefix:
path = path.split('.', 1)[-1]
new_target.name = path
new_condition.target = [new_target]
path_prefix, constraints = next(self.get_constraints(create=True,
path_prefix=variable.path_prefix,
))
if not hasattr(constraints, 'condition'):
constraints.condition = []
constraints.condition.append(new_condition)
def check_source_target(self, constraints):
"""verify that source != target in condition
"""
@ -404,7 +361,7 @@ class Annotator(TargetAnnotator, ParamAnnotator, Walk):
main_action,
)
if isinstance(leader_or_variable, self.objectspace.variable) and \
(leader_or_variable.auto_save or leader_or_variable.auto_freeze) and \
leader_or_variable.auto_save and \
'force_default_on_freeze' in actions:
continue
for action in actions[1:]:

View file

@ -54,8 +54,9 @@ class Annotator(Walk):
objectspace,
*args,
):
self.mode_auto = []
self.objectspace = objectspace
if not hasattr(self.objectspace.space, 'variables'):
if not self.objectspace.paths:
return
self.modes = {name: Mode(idx) for idx, name in enumerate(self.objectspace.rougailconfig['modes_level'])}
self.remove_empty_families()
@ -67,34 +68,34 @@ class Annotator(Walk):
def remove_empty_families(self) -> None:
"""Remove all families without any variable
"""
removed_families = {}
for family, parent in self.get_families(with_parent=True):
if isinstance(family, self.objectspace.family) and not self._has_variable(family):
removed_families.setdefault(parent, []).append(family)
for parent, families in removed_families.items():
for family in families:
del parent.variable[family.name]
removed_families = []
for family in self.get_families():
if isinstance(family, self.objectspace.family) and not self._has_variable(family.path):
if '.' in family.path:
removed_families.append(family.path)
removed_families.reverse()
for family in removed_families:
self.objectspace.del_family(family)
def _has_variable(self,
family: 'self.objectspace.family',
family: str,
) -> bool:
if hasattr(family, 'variable'):
for variable in family.variable.values():
if isinstance(variable, self.objectspace.family):
if self._has_variable(variable):
return True
else:
for variable in self.objectspace.parents[family]:
if variable in self.objectspace.families:
if self._has_variable(variable):
return True
else:
return True
return False
def family_names(self) -> None:
"""Set doc, path, ... to family
"""
for family in self.get_families():
if not hasattr(family, 'description'):
if not family.description:
family.description = family.name
family.doc = family.description
del family.description
# family.doc = family.description
# del family.description
def change_modes(self):
"""change the mode of variables
@ -130,17 +131,21 @@ class Annotator(Walk):
def _set_default_mode(self,
family: 'self.objectspace.family',
) -> None:
if not hasattr(family, 'variable'):
children = self.objectspace.parents[family.path]
if not children:
return
if self._has_mode(family):
family_mode = family.mode
else:
family_mode = None
leader = None
for variable in family.variable.values():
if leader is None and hasattr(family, 'leadership') and family.leadership:
for variable_path in children:
variable = self.objectspace.paths[variable_path]
if variable.type == 'symlink':
continue
if leader is None and family.type == 'leadership':
leader = variable
if isinstance(variable, self.objectspace.family):
if variable_path in self.objectspace.families:
# set default mode a subfamily
if family_mode and not self._has_mode(variable):
self._set_auto_mode(variable, family_mode)
@ -154,32 +159,33 @@ class Annotator(Walk):
# here because follower can change leader mode
self._set_auto_mode(family, leader.mode)
@staticmethod
def _has_mode(obj) -> bool:
return 'mode' in vars(obj) and not hasattr(obj, 'mode_auto')
def _has_mode(self, obj) -> bool:
return obj.mode and not obj.path in self.mode_auto
def _set_default_mode_variable(self,
variable: 'self.objectspace.variable',
family_mode: str,
) -> None:
# auto_save or auto_freeze variable is set to 'basic' mode
# auto_save variable is set to 'basic' mode
# if its mode is not defined by the user
if not self._has_mode(variable) and \
(variable.auto_save is True or variable.auto_freeze is True):
variable.auto_save is True:
variable.mode = self.objectspace.rougailconfig['modes_level'][0]
# mandatory variable without value is a basic variable
elif not self._has_mode(variable) and \
variable.mandatory is True and \
not hasattr(variable, 'default') and \
not hasattr(variable, 'default_multi'):
variable.default is None and \
variable.path not in self.objectspace.default_multi:
variable.mode = self.objectspace.rougailconfig['modes_level'][0]
elif family_mode and not self._has_mode(variable):
self._set_auto_mode(variable, family_mode)
@staticmethod
def _set_auto_mode(obj, mode: str) -> None:
def _set_auto_mode(self,
obj,
mode: str,
) -> None:
obj.mode = mode
obj.mode_auto = True
self.mode_auto.append(obj.path)
def _set_default_mode_leader(self,
leader: 'self.objectspace.variable',
@ -188,12 +194,9 @@ class Annotator(Walk):
if follower.auto_save is True:
msg = _(f'leader/followers "{follower.name}" could not be auto_save')
raise DictConsistencyError(msg, 29, follower.xmlfiles)
if follower.auto_freeze is True:
msg = f'leader/followers "{follower.name}" could not be auto_freeze'
raise DictConsistencyError(_(msg), 30, follower.xmlfiles)
if leader == follower:
# it's a leader
if not hasattr(leader, 'mode'):
if not leader.mode:
self._set_auto_mode(leader, self.objectspace.rougailconfig['default_variable_mode'])
return
if self._has_mode(follower):
@ -215,44 +218,42 @@ class Annotator(Walk):
def _change_family_mode(self,
family: 'self.objectspace.family',
) -> None:
if hasattr(family, 'mode'):
if family.mode:
family_mode = family.mode
else:
family_mode = self.objectspace.rougailconfig['default_family_mode']
min_variable_mode = self.objectspace.rougailconfig['modes_level'][-1]
# change variable mode, but not if variables are not in a family
is_leadership = hasattr(family, 'leadership') and family.leadership
if hasattr(family, 'variable'):
for idx, variable in enumerate(family.variable.values()):
if isinstance(variable, self.objectspace.family):
if not hasattr(variable, 'mode'):
is_leadership = family.type == 'leadership'
if family.path in self.objectspace.parents:
for idx, variable_path in enumerate(self.objectspace.parents[family.path]):
variable = self.objectspace.paths[variable_path]
if variable.type == 'symlink':
continue
if variable_path in self.objectspace.families:
if not variable.mode:
variable.mode = self.objectspace.rougailconfig['default_family_mode']
#elif idx == 0 and is_leadership:
# variable.mode = None
# if variable.path == 'general.piwigo.users.piwigo_users':
# print(variable.path)
# continue
else:
self._change_variable_mode(variable, family_mode, is_leadership)
if self.modes[min_variable_mode] > self.modes[variable.mode]:
min_variable_mode = variable.mode
if not isinstance(family, (self.objectspace.family, self.objectspace.variables)):
# it's Variable, Service, ...
return
if not hasattr(family, 'mode'):
#FIXME if not isinstance(family, (self.objectspace.family, self.objectspace.variables)):
# # it's Variable, Service, ...
# return
if not family.mode:
# set the lower variable mode to family
self._set_auto_mode(family, min_variable_mode)
if not is_leadership and family.mode != min_variable_mode:
msg = _(f'the family "{family.name}" is in "{family.mode}" mode but variables and '
f'families inside have the higher modes "{min_variable_mode}"')
raise DictConsistencyError(msg, 62, family.xmlfiles)
#FIXME if not is_leadership and family.mode != min_variable_mode:
#FIXME msg = _(f'the family "{family.name}" is in "{family.mode}" mode but variables and '
#FIXME f'families inside have the higher modes "{min_variable_mode}"')
#FIXME raise DictConsistencyError(msg, 62, family.xmlfiles)
def _change_variable_mode(self,
variable,
family_mode: str,
is_follower: bool,
) -> None:
if hasattr(variable, 'mode'):
if variable.mode:
variable_mode = variable.mode
else:
variable_mode = self.objectspace.rougailconfig['default_variable_mode']
@ -263,28 +264,21 @@ class Annotator(Walk):
f'but family has the higher family mode "{family_mode}"')
raise DictConsistencyError(msg, 61, variable.xmlfiles)
self._set_auto_mode(variable, family_mode)
if not hasattr(variable, 'mode'):
if not variable.mode:
variable.mode = variable_mode
def dynamic_families(self):
"""link dynamic families to object
"""
for family in self.get_families():
if 'dynamic' not in vars(family):
if family.type != 'dynamic':
continue
family.suffixes = self.objectspace.paths.get_variable(family.dynamic,
family.namespace,
xmlfiles=family.xmlfiles,
allow_variable_namespace=True,
force_path_prefix=family.path_prefix,
add_path_prefix=True,
)
del family.dynamic
if not family.suffixes.multi:
family.variable = self.objectspace.paths[family.variable]
if not family.variable.multi:
msg = _(f'dynamic family "{family.name}" must be linked '
f'to multi variable')
raise DictConsistencyError(msg, 16, family.xmlfiles)
for variable in family.variable.values():
for variable in self.objectspace.parents[family.path]:
if isinstance(variable, self.objectspace.family) and not variable.leadership:
msg = _(f'dynamic family "{family.name}" cannot contains another family')
raise DictConsistencyError(msg, 22, family.xmlfiles)
@ -293,9 +287,7 @@ class Annotator(Walk):
"""Convert variable help
"""
for family in self.get_families():
if not hasattr(family, 'help'):
if not family.help:
continue
if not hasattr(family, 'information'):
family.information = self.objectspace.information(family.xmlfiles)
family.information.help = family.help
self.objectspace.informations.add(family.path, 'help', family.help)
del family.help

View file

@ -40,71 +40,6 @@ from jinja2 import Undefined, UndefinedError, DictLoader
from jinja2.utils import missing
class CollectUndefined(Undefined):
def __init__(
self,
hint=None,
obj=missing,
name=None,
exc=UndefinedError,
subname=None,
) -> None:
self._undefined_hint = hint
self._undefined_obj = obj
self._undefined_name = name
self._undefined_exception = exc
if subname:
full_name = subname + '.'
else:
full_name = ''
full_name += self._undefined_name
self.variables.add(full_name)
self._undefined_full_name = full_name
def __getattr__(self, name):
return CollectUndefined(name=name, subname=self._undefined_full_name)
def get_jinja_variable_to_param(jinja_text,
objectspace,
obj,
path_prefix,
variable_name,
variable_path,
):
CollectUndefined.variables = set()
try:
SandboxedEnvironment(loader=DictLoader({'tmpl': jinja_text}), undefined=CollectUndefined).get_template('tmpl').render()
except UndefinedError as err:
msg = _(f'error in jinja "{jinja_text}": {err}')
raise DictConsistencyError(msg, 91, obj.xmlfiles) from err
variables = list(CollectUndefined.variables)
variables.sort()
for variable in variables:
new_param = objectspace.param(obj.xmlfiles)
if variable in [variable_name, variable_path]:
new_param.name = '__internal_key'
new_param.type = 'string'
new_param.text = variable
else:
new_param.name = variable
new_param.text = variable
try:
set_variable_to_param(new_param,
objectspace,
None,
obj.namespace,
path_prefix,
None,
)
except DictConsistencyError as err:
if err.errno != 42:
raise err from err
continue
new_param.type = 'variable'
obj.param.append(new_param)
CALC_MULTI = ('calc_value',
'calc_list',
'get_range',
@ -127,9 +62,9 @@ class Annotator(TargetAnnotator, ParamAnnotator):
level = 60
def __init__(self,
objectspace,
functions,
*args,
):
return
self.objectspace = objectspace
self.functions = copy(functions)
self.functions.extend(self.objectspace.rougailconfig['internal_functions'])

View file

@ -1,85 +0,0 @@
"""Annotate group
Created by:
EOLE (http://eole.orion.education.fr)
Copyright (C) 2005-2018
Forked by:
Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2023
distribued with GPL-2 or later license
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from rougail.i18n import _
from rougail.error import DictConsistencyError
from rougail.utils import normalize_family
from rougail.annotator.variable import Walk
class Annotator(Walk):
"""Annotate group
"""
level = 10
def __init__(self,
objectspace,
*args,
):
if not hasattr(objectspace.space, 'variables'):
return
self.objectspace = objectspace
self.convert_groups()
def convert_groups(self): # pylint: disable=C0111
"""convert groups
"""
# store old leaders family name
for family in self.get_families():
if not isinstance(family, self.objectspace.family):
continue
if not family.leadership:
continue
if hasattr(family, 'dynamic'):
msg = _(f'the family "{family.name}" cannot be leadership and dynamic together')
raise DictConsistencyError(msg, 31, family.xmlfiles)
if not hasattr(family, 'variable'):
continue
for idx, variable in enumerate(family.variable.values()):
if idx == 0:
# it's a leader
if variable.multi is not True:
msg = _(f'the variable "{variable.name}" in a leadership must be multi')
raise DictConsistencyError(msg, 32, variable.xmlfiles)
if variable.hidden:
family.hidden = variable.hidden
elif family.hidden:
variable.hidden = family.hidden
if variable.hidden:
variable.frozen = True
variable.force_default_on_freeze = True
variable.hidden = None
else:
# it's a follower
if family.hidden:
variable.frozen = True
variable.force_default_on_freeze = True
if variable.multi is True:
variable.multi = 'submulti'
else:
variable.multi = True

View file

@ -27,9 +27,11 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from typing import Union
from rougail.i18n import _
from rougail.error import DictConsistencyError
from rougail.annotator.variable import Walk
from rougail.object_model import Calculation
PROPERTIES = ('hidden', 'frozen', 'force_default_on_freeze',
@ -45,88 +47,103 @@ class Annotator(Walk):
*args
) -> None:
self.objectspace = objectspace
services = []
if not self.objectspace.paths.has_path_prefix() and hasattr(self.objectspace.space, 'services'):
services.append(self.objectspace.space.services)
elif hasattr(self.objectspace.space, 'variables'):
for path_prefix in self.objectspace.paths.get_path_prefixes():
if path_prefix in self.objectspace.space.variables and \
hasattr(self.objectspace.space.variables[path_prefix], 'services'):
services.append(self.objectspace.space.variables[path_prefix].services)
for service in services:
self.convert_services(service)
if hasattr(self.objectspace.space, 'variables'):
self.frozen = {}
if self.objectspace.paths:
self.convert_family()
self.convert_variable()
def convert_property(self,
variable,
) -> None:
"""convert properties
"""
# hidden variable is also frozen
if isinstance(variable, self.objectspace.variable) and variable.hidden is True and \
variable.name != self.objectspace.rougailconfig['auto_freeze_variable']:
if not variable.auto_freeze and \
not hasattr(variable, 'provider') and not hasattr(variable, 'supplier'):
variable.frozen = True
if not variable.auto_save and \
not variable.auto_freeze and \
'force_default_on_freeze' not in vars(variable) and \
not hasattr(variable, 'provider') and not hasattr(variable, 'supplier'):
variable.force_default_on_freeze = True
if not hasattr(variable, 'properties'):
variable.properties = []
if 'mandatory' in vars(variable) and not variable.mandatory and variable.multi:
# a multi could not have "None" has value
# to permit it, just add mandatory="False"
variable.properties.append('notempty')
for prop in PROPERTIES:
if hasattr(variable, prop):
if getattr(variable, prop) is True:
# for subprop in CONVERT_PROPERTIES.get(prop, [prop]):
variable.properties.append(prop)
setattr(variable, prop, None)
if hasattr(variable, 'unique') and variable.unique != 'nil':
if variable.unique == 'False' or variable.unique is False:
variable.properties.append('notunique')
else:
variable.properties.append('unique')
if hasattr(variable, 'mode') and variable.mode:
variable.properties.append(variable.mode)
variable.mode = None
if 'force_store_value' in variable.properties and \
'force_default_on_freeze' in variable.properties: # pragma: no cover
# should not appened
msg = _('cannot have auto_freeze or auto_save with the hidden '
f'variable "{variable.name}"')
raise DictConsistencyError(msg, 50, variable.xmlfiles)
if not variable.properties:
del variable.properties
def convert_services(self, services) -> None:
"""convert services
"""
self.convert_property(services)
for services_ in services.service.values():
self.convert_property(services_)
for service in vars(services_).values():
if not isinstance(service, self.objectspace.family):
continue
self.convert_property(service)
for family in service.family:
self.convert_property(family)
for variable in family.variable:
self.convert_property(variable)
def convert_family(self) -> None:
"""convert families
"""
for family in self.get_families():
self.convert_property(family)
self._convert_property(family)
# collect for force_default_on_freeze
if family.hidden:
self.set_variable_frozen(family.path,
family.hidden,
)
def set_variable_frozen(self,
family_path: str,
hidden: Union[bool, Calculation],
) -> None:
for variable_path in self.objectspace.parents[family_path]:
if variable_path in self.objectspace.families:
# it's a family
self.set_variable_frozen(variable_path,
hidden,
)
else:
# it's a variable
variable = self.objectspace.paths[variable_path]
# if frozen is already true or hidden for variable is true => always frozen
if self.frozen.get(variable.path) is True or variable.hidden is True or hidden is True:
self.frozen[variable.path] = True
elif variable.path in self.frozen:
self.frozen[variable.path].append(hidden)
else:
self.frozen[variable.path] = [hidden]
def convert_variable(self) -> None:
"""convert variables
"""
for variable in self.get_variables():
self.convert_property(variable)
if variable.path.startswith('services.'):
continue
if variable.type == 'symlink':
continue
self._convert_variable_property(variable)
def _convert_variable_property(self,
variable: dict,
) -> None:
"""convert properties
"""
path = variable.path
self._convert_property(variable)
if variable.hidden:
if variable.hidden is True:
self.frozen[variable.path] = True
elif self.frozen.get(variable.path) is not True:
self.frozen.setdefault(variable.path, []).append(variable.hidden)
if variable.path in self.frozen:
frozen = self.frozen[variable.path]
if frozen is True:
value = True
else:
value = []
for calculation in frozen:
calculation_object = calculation.__class__
calculation_dict = calculation.dict().copy()
calculation_dict['attribute_name'] = 'frozen'
calculation_dict['path'] = variable.path
value.append(calculation_object(**calculation_dict))
if len(value) == 1:
value = value[0]
self.objectspace.properties.add(path, 'frozen', value)
if not variable.auto_save:
# if auto_save, save calculated value
self.objectspace.properties.add(path, 'force_default_on_freeze', True)
if variable.mandatory and variable.multi:
# a multi could not have "None" has value
# to permit it, just add mandatory="False"
self.objectspace.properties.add(path, 'notempty', True)
if variable.unique:
self.objectspace.properties.add(path, 'unique', True)
if variable.unique is False:
self.objectspace.properties.add(path, 'notunique', True)
if variable.auto_save:
self.objectspace.properties.add(variable.path, 'force_store_value', True)
def _convert_property(self,
obj: dict,
) -> None:
for prop in PROPERTIES:
if not hasattr(obj, prop):
continue
value = getattr(obj, prop)
if not value:
continue
self.objectspace.properties.add(obj.path, prop, value)
if obj.mode:
self.objectspace.properties.add(obj.path, obj.mode, True)

View file

@ -1,420 +0,0 @@
"""Annotate services
Created by:
EOLE (http://eole.orion.education.fr)
Copyright (C) 2005-2018
Forked by:
Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2023
distribued with GPL-2 or later license
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from os.path import basename
from typing import Tuple
from rougail.i18n import _
from rougail.utils import normalize_family
from rougail.error import DictConsistencyError
from rougail.annotator.variable import CONVERT_OPTION
try:
import tiramisu4 as tiramisu
except ModuleNotFoundError:
import tiramisu
# a object's attribute has some annotations
# that shall not be present in the exported (flatened) XML
ERASED_ATTRIBUTES = ('redefine', 'namespace', 'xmlfiles', 'disabled', 'name', 'manage')
ERASED_ATTRIBUTES2 = ('redefine', 'namespace', 'xmlfiles', 'disabled')
ALLOW_ATTRIBUT_NOT_MANAGE = ['file', 'engine', 'target', 'certificate']
FORCE_INFORMATIONS = ['mode']
class Annotator:
"""Manage service's object
for example::
<services>
<service name="test">
<service_access service='ntp'>
</service_access>
</service>
</services>
"""
level = 20
def __init__(self,
objectspace,
*args,
) -> None:
self.objectspace = objectspace
self.uniq_overrides = {}
if 'network_type' not in self.objectspace.types:
self.objectspace.types['network_type'] = self.objectspace.types['ip_type']
services = []
if not self.objectspace.paths.has_path_prefix():
self.uniq_overrides[None] = []
if hasattr(self.objectspace.space, 'services'):
if not hasattr(self.objectspace.space.services, 'service'):
del self.objectspace.space.services
else:
services.append((None, 'services', self.objectspace.space.services))
elif hasattr(self.objectspace.space, 'variables'):
for path_prefix in self.objectspace.paths.get_path_prefixes():
self.uniq_overrides[path_prefix] = []
root_path = f'{path_prefix}.services'
if not path_prefix in self.objectspace.space.variables or \
not hasattr(self.objectspace.space.variables[path_prefix], 'services'):
continue
if not hasattr(self.objectspace.space.variables[path_prefix].services, 'service'):
del self.objectspace.space.variables[path_prefix].services
else:
services.append((path_prefix, root_path, self.objectspace.space.variables[path_prefix].services))
for path_prefix, root_path, service in services:
self.convert_services(path_prefix, root_path, service)
def convert_services(self, path_prefix, root_path, services):
"""convert services to variables
"""
services.hidden = True
services.name = 'services'
services.doc = 'services'
services.path = root_path
for service_name, service in services.service.items():
service.name = normalize_family(service_name)
activate_obj = self._generate_element('boolean',
None,
None,
'activate',
not service.disabled,
service,
f'{root_path}.{service.name}',
path_prefix,
)
service.disabled = None
dico = dict(vars(service))
if 'type' not in dico:
if service.manage:
dico['type'] = service.type
else:
dico['type'] = 'none'
for elttype, values in dico.items():
if elttype == 'servicelist':
self.objectspace.paths.list_conditions[path_prefix].setdefault('servicelist',
{}).setdefault(
values,
[]).append(activate_obj)
continue
if elttype in ERASED_ATTRIBUTES:
continue
if not service.manage and elttype not in ALLOW_ATTRIBUT_NOT_MANAGE and (elttype != 'type' or values != 'none'):
msg = _(f'unmanage service cannot have "{elttype}"')
raise DictConsistencyError(msg, 66, service.xmlfiles)
if isinstance(values, (dict, list)):
if elttype != 'ip':
eltname = elttype + 's'
else:
eltname = elttype
if hasattr(service, 'servicelist'):
if isinstance(values, dict):
for key, value in values.items():
setattr(value, 'servicelist', service.servicelist)
family = self._gen_family(eltname,
f'{root_path}.{service.name}',
service.xmlfiles,
path_prefix,
with_informations=False,
)
if isinstance(values, dict):
values = list(values.values())
family.family = self.make_group_from_elts(service_name,
elttype,
values,
f'{root_path}.{service.name}.{eltname}',
root_path,
path_prefix,
)
setattr(service, elttype, family)
else:
if not hasattr(service, 'information'):
service.information = self.objectspace.information(service.xmlfiles)
setattr(service.information, elttype, values)
service.path = f'{root_path}.{service.name}'
manage = self._generate_element('boolean',
None,
None,
'manage',
service.manage,
service,
f'{root_path}.{service.name}',
path_prefix,
)
service.variable = [activate_obj, manage]
service.doc = service_name
def make_group_from_elts(self,
service_name,
elttype,
elts,
path,
root_path,
path_prefix,
):
"""Splits each objects into a group (and `OptionDescription`, in tiramisu terms)
and build elements and its attributes (the `Options` in tiramisu terms)
"""
families = []
listname = '{}list'.format(elttype)
for elt in elts:
# try to launch _update_xxxx() function
update_elt = '_update_' + elttype
if hasattr(self, update_elt):
getattr(self, update_elt)(elt,
service_name,
path_prefix,
)
c_name, subpath = self._get_name_path(elt,
path,
root_path,
path_prefix,
)
family = self._gen_family(c_name,
subpath.rsplit('.', 1)[0],
elt.xmlfiles,
path_prefix,
)
family.variable = []
if hasattr(elt, 'disabled'):
disabled = elt.disabled
else:
disabled = False
activate_obj = self._generate_element('boolean',
None,
None,
'activate',
not disabled,
elt,
subpath,
path_prefix,
)
for key in dir(elt):
if key.startswith('_') or key.endswith('_type') or key in ERASED_ATTRIBUTES2:
continue
value = getattr(elt, key)
if key in [listname, 'servicelist']:
self.objectspace.paths.list_conditions[path_prefix].setdefault(key,
{}).setdefault(
value,
[]).append(activate_obj)
continue
if key == 'name':
dtd_key_type = elttype + '_type'
else:
dtd_key_type = key + '_type'
elt_type = getattr(elt, dtd_key_type, None)
if elt_type:
try:
value = CONVERT_OPTION.get(elt_type, {}).get('func', str)(value)
except ValueError as err:
msg = _(f'"{value}" is not a valid "{elttype}": {err}')
raise DictConsistencyError(msg, 93, elt.xmlfiles)
if key not in FORCE_INFORMATIONS and elt_type:
if elt_type == 'variable':
elt_type = 'symlink'
family.variable.append(self._generate_element(elt_type,
dtd_key_type,
elttype,
key,
value,
elt,
subpath,
path_prefix,
))
else:
setattr(family.information, key, value)
family.variable.append(activate_obj)
families.append(family)
return families
def _get_name_path(self,
elt,
path: str,
root_path: str,
path_prefix: str,
) -> Tuple[str, str]:
# create element name, if already exists, add _xx to be uniq
if hasattr(elt, 'source') and elt.source:
name = elt.source
else:
name = elt.name
idx = 0
while True:
c_name = name
if idx:
c_name += f'_{idx}'
subpath = '{}.{}'.format(path, normalize_family(c_name))
try:
self.objectspace.paths.get_family(subpath, root_path, path_prefix)
except DictConsistencyError as err:
if err.errno == 42:
return c_name, subpath
idx += 1
def _gen_family(self,
name,
subpath,
xmlfiles,
path_prefix,
with_informations=True,
):
family = self.objectspace.family(xmlfiles)
family.name = normalize_family(name)
family.doc = name
family.mode = None
self.objectspace.paths.add_family('services',
subpath,
family,
False,
force_path_prefix=path_prefix,
)
if with_informations:
family.information = self.objectspace.information(xmlfiles)
return family
def _generate_element(self,
type_,
dtd_key_type,
elttype,
key,
value,
elt,
path,
path_prefix,
): # pylint: disable=R0913
variable = self.objectspace.variable(elt.xmlfiles)
variable.name = normalize_family(key)
variable.mode = None
variable.type = type_
if type_ == 'symlink':
variable.opt = self.objectspace.paths.get_variable(value,
self.objectspace.rougailconfig['variable_namespace'],
xmlfiles=elt.xmlfiles,
force_path_prefix=path_prefix,
add_path_prefix=True,
)
variable.multi = None
needed_type = self.objectspace.types[dtd_key_type]
if elttype != 'certificate' and needed_type not in ('variable', variable.opt.type):
msg = _(f'"{value}" in "{elttype}" must be a variable with type '
f'"{needed_type}" not "{variable.opt.type}"')
raise DictConsistencyError(msg, 58, elt.xmlfiles)
else:
variable.doc = key
variable.default = value
variable.namespace = 'services'
self.objectspace.paths.add_variable('services',
path,
variable,
force_path_prefix=path_prefix
)
return variable
def _update_override(self,
override,
service_name,
path_prefix,
):
if service_name in self.uniq_overrides[path_prefix]:
msg = _('only one override is allowed by service, '
'please use a variable multiple if you want have more than one IP')
raise DictConsistencyError(msg, 69, override.xmlfiles)
self.uniq_overrides[path_prefix].append(service_name)
override.name = service_name
if not hasattr(override, 'source'):
override.source = service_name
@staticmethod
def _update_file(file_,
service_name,
path_prefix,
):
if not hasattr(file_, 'file_type') or file_.file_type == "filename":
if not hasattr(file_, 'source'):
file_.source = basename(file_.name)
elif not hasattr(file_, 'source'):
msg = _(f'attribute "source" is mandatory for the file "{file_.name}" '
f'"({service_name})"')
raise DictConsistencyError(msg, 34, file_.xmlfiles)
def _update_ip(self,
ip,
service_name,
path_prefix,
) -> None:
variable = self.objectspace.paths.get_variable(ip.name,
ip.namespace,
xmlfiles=ip.xmlfiles,
force_path_prefix=path_prefix,
add_path_prefix=True,
)
if variable.type not in ['ip', 'network', 'network_cidr']:
msg = _(f'ip cannot be linked to "{variable.type}" variable "{ip.name}"')
raise DictConsistencyError(msg, 70, ip.xmlfiles)
if variable.type in ['ip', 'network_cidr'] and hasattr(ip, 'netmask'):
msg = _(f'ip with ip_type "{variable.type}" must not have netmask')
raise DictConsistencyError(msg, 59, ip.xmlfiles)
if variable.type == 'network' and not hasattr(ip, 'netmask'):
msg = _(f'ip with ip_type "{variable.type}" must have netmask')
raise DictConsistencyError(msg, 64, ip.xmlfiles)
if hasattr(ip, 'netmask'):
netmask = self.objectspace.paths.get_variable(ip.netmask,
ip.namespace,
xmlfiles=ip.xmlfiles,
force_path_prefix=path_prefix,
add_path_prefix=True,
)
if netmask.type != 'netmask':
msg = _(f'netmask in ip must have type "netmask", not "{netmask.type}"')
raise DictConsistencyError(msg, 65, ip.xmlfiles)
def _update_certificate(self,
certificate,
certificate_name,
path_prefix,
) -> None:
if certificate.certificate_type == "variable":
variable = self.objectspace.paths.get_variable(certificate.name,
certificate.namespace,
xmlfiles=certificate.xmlfiles,
force_path_prefix=path_prefix,
add_path_prefix=True,
)
certificate.catype = certificate.type
if not hasattr(certificate, 'domain'):
certificate.domain = self.objectspace.rougailconfig['default_certificate_domain']
variable = self.objectspace.paths.get_variable(certificate.domain,
certificate.namespace,
xmlfiles=certificate.xmlfiles,
force_path_prefix=path_prefix,
add_path_prefix=True,
)
if variable.type != 'domainname':
msg = _(f'the certificate "{certificate.name}" has an attribute "domain" linked with a "{variable.type}" variable ("{certificate.domain}"), but must be a "domainename" variable')
raise DictConsistencyError(msg, 94, certificate.xmlfiles)

View file

@ -31,6 +31,8 @@ from rougail.annotator.variable import Walk
from rougail.i18n import _
from rougail.error import DictConsistencyError
from rougail.object_model import Calculation
class Annotator(Walk): # pylint: disable=R0903
"""Annotate value
@ -40,7 +42,7 @@ class Annotator(Walk): # pylint: disable=R0903
objectspace,
*args,
) -> None:
if not hasattr(objectspace.space, 'variables'):
if not objectspace.paths:
return
self.objectspace = objectspace
self.convert_value()
@ -50,52 +52,56 @@ class Annotator(Walk): # pylint: disable=R0903
"""convert value
"""
for variable in self.get_variables():
if variable.type == 'symlink':
continue
self._convert_value(variable)
def _convert_value(self,
variable,
variable: dict,
) -> None:
multi = self.objectspace.multis.get(variable.path, False)
# a boolean must have value, the default value is "True"
if not hasattr(variable, 'value') and variable.type == 'boolean':
new_value = self.objectspace.value(variable.xmlfiles)
new_value.name = True
new_value.type = 'boolean'
variable.value = [new_value]
# if the variable is mandatory and doesn't have any value
# then the variable's mode is set to 'basic'
# variable with default value is mandatory
if hasattr(variable, 'value') and variable.value:
has_value = True
for value in variable.value:
if value.type == 'calculation' or value.type == 'nil':
has_value = False
break
if has_value and 'mandatory' not in vars(variable):
# if has value without any calculation
variable.mandatory = True
if not hasattr(variable, 'value'):
if variable.type == 'boolean' and \
multi is False and \
variable.default is None:
variable.default = True
if variable.default is None:
return
if variable.value[0].type == 'calculation':
variable.default = variable.value[0]
elif variable.multi:
if self.objectspace.paths.is_follower(variable):
if variable.multi != 'submulti' and len(variable.value) != 1:
has_value = False
if isinstance(variable.default, Calculation):
pass
# variable.default = variable.default.to_function(self.functions)
elif isinstance(variable.default, list):
if not multi:
raise Exception(f'The variable "{variable.path}" with a list has default value must have "multi" attribute')
if variable.path in self.objectspace.followers:
if multi != 'submulti' and len(variable.default) != 1:
msg = _(f'the follower "{variable.name}" without multi attribute can only have one value')
raise DictConsistencyError(msg, 87, variable.xmlfiles)
else:
variable.default = [value.name for value in variable.value]
if not self.objectspace.paths.is_leader(variable):
if variable.multi == 'submulti':
variable.default_multi = [value.name for value in variable.value]
# else:
# variable.default = [value.name for value in variable.default]
if variable.path not in self.objectspace.leaders:
if multi == 'submulti':
self.objectspace.default_multi[variable.path] = variable.default #[value.name for value in variable.value]
variable.default = None
else:
variable.default_multi = variable.value[0].name
self.objectspace.default_multi[variable.path] = variable.default[0] #.name
has_value = True
elif variable.multi:
#msg = _(f'the none multi variable "{variable.name}" cannot have '
# 'more than one value')
#raise DictConsistencyError(msg, 68, variable.xmlfiles)
raise Exception('pfff')
else:
if len(variable.value) > 1:
msg = _(f'the none multi variable "{variable.name}" cannot have '
'more than one value')
raise DictConsistencyError(msg, 68, variable.xmlfiles)
variable.default = variable.value[0].name
del variable.value
if variable.path in self.objectspace.followers:
self.objectspace.default_multi[variable.path] = variable.default
variable.default = None
has_value = True
# variable with default value is mandatory
if has_value and variable.mandatory is None:
# if has value without any calculation
variable.mandatory = True
def add_choice_nil(self) -> None:
"""A variable with type "Choice" that is not mandatory must has "nil" value
@ -104,11 +110,11 @@ class Annotator(Walk): # pylint: disable=R0903
if variable.type != 'choice':
continue
is_none = False
for choice in variable.choice:
if choice.type == 'nil':
if isinstance(variable.choices, Calculation):
continue
for choice in variable.choices:
if choice is None:
is_none = True
break
if not variable.mandatory and not is_none:
choice = self.objectspace.choice(variable.xmlfiles)
choice.name = None
choice.type = 'nil'
variable.choice.append(choice)
variable.choices.append(None)

View file

@ -30,26 +30,24 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from rougail.i18n import _
from rougail.error import DictConsistencyError
from rougail.objspace import convert_boolean, get_variables
from rougail.objspace import convert_boolean #, get_variables
from rougail.object_model import Calculation
CONVERT_OPTION = {'number': dict(opttype="IntOption", func=int),
CONVERT_OPTION = {'string': dict(opttype="StrOption"),
'number': dict(opttype="IntOption", func=int),
'float': dict(opttype="FloatOption", func=float),
'choice': dict(opttype="ChoiceOption"),
'string': dict(opttype="StrOption"),
'password': dict(opttype="PasswordOption"),
'boolean': dict(opttype="BoolOption", func=convert_boolean),
'secret': dict(opttype="PasswordOption"),
'mail': dict(opttype="EmailOption"),
'boolean': dict(opttype="BoolOption", func=convert_boolean),
'symlink': dict(opttype="SymLinkOption"),
'filename': dict(opttype="FilenameOption"),
'date': dict(opttype="DateOption"),
'unix_user': dict(opttype="UsernameOption"),
'ip': dict(opttype="IPOption", initkwargs={'allow_reserved': True}),
'local_ip': dict(opttype="IPOption", initkwargs={'private_only': True,
'warnings_only': True}),
'cidr': dict(opttype="IPOption", initkwargs={'cidr': True}),
'netmask': dict(opttype="NetmaskOption"),
'network': dict(opttype="NetworkOption"),
'network_cidr': dict(opttype="NetworkOption", initkwargs={'cidr': True}),
'broadcast': dict(opttype="BroadcastOption"),
'netbios': dict(opttype="DomainnameOption", initkwargs={'type': 'netbios',
'warnings_only': True}),
@ -57,13 +55,14 @@ CONVERT_OPTION = {'number': dict(opttype="IntOption", func=int),
'allow_ip': False}),
'hostname': dict(opttype="DomainnameOption", initkwargs={'type': 'hostname',
'allow_ip': False}),
'web_address': dict(opttype="URLOption", initkwargs={'allow_ip': True,
'web_address': dict(opttype="URLOption", initkwargs={'allow_ip': False,
'allow_without_dot': True}),
'port': dict(opttype="PortOption", initkwargs={'allow_private': True, 'allow_protocol': True}),
'port': dict(opttype="PortOption", initkwargs={'allow_private': True}),
'mac': dict(opttype="MACOption"),
'cidr': dict(opttype="IPOption", initkwargs={'cidr': True}),
'network_cidr': dict(opttype="NetworkOption", initkwargs={'cidr': True}),
'unix_permissions': dict(opttype="PermissionsOption", initkwargs={'warnings_only': True}, func=int),
'choice': dict(opttype="ChoiceOption"),
#
'symlink': dict(opttype="SymLinkOption"),
}
@ -75,56 +74,15 @@ class Walk:
def get_variables(self):
"""Iter all variables from the objectspace
"""
yield from get_variables(self.objectspace)
for path in self.objectspace.variables:
yield self.objectspace.paths[path]
# yield from get_variables(self.objectspace)
def get_families(self,
with_parent: bool=False,
):
def get_families(self):
"""Iter all families from the objectspace
"""
for family in self.objectspace.space.variables.values():
yield from self._get_families(family, None, with_parent)
def _get_families(self,
family: 'self.objectspace.family',
old_family: 'self.objectspace.family',
with_parent: bool,
):
if with_parent:
yield family, old_family,
if hasattr(family, 'variable'):
if not with_parent:
yield family
for fam in family.variable.values():
if isinstance(fam, self.objectspace.family):
yield from self._get_families(fam, family, with_parent)
if hasattr(family, 'variables'):
for fam in family.variables.values():
yield from self._get_families(fam, family, with_parent)
def get_constraints(self,
create: bool=False,
path_prefix: str=None,
):
if not self.objectspace.paths.has_path_prefix():
if hasattr(self.objectspace.space, 'constraints'):
yield None, self.objectspace.space.constraints
elif create:
self.objectspace.space.constraints = self.objectspace.constraints(None)
yield None, self.objectspace.space.constraints
else:
if path_prefix:
path_prefixes = [path_prefix]
else:
path_prefixes = self.objectspace.paths.get_path_prefixes()
for path_prefix in path_prefixes:
if hasattr(self.objectspace.space, 'variables') and \
path_prefix in self.objectspace.space.variables and \
hasattr(self.objectspace.space.variables[path_prefix], 'constraints'):
yield path_prefix, self.objectspace.space.variables[path_prefix].constraints
elif create:
self.objectspace.space.variables[path_prefix].constraints = self.objectspace.constraints(None)
yield path_prefix, self.objectspace.space.variables[path_prefix].constraints
for path in self.objectspace.families:
yield self.objectspace.paths[path]
class Annotator(Walk): # pylint: disable=R0903
@ -135,7 +93,7 @@ class Annotator(Walk): # pylint: disable=R0903
objectspace,
*args,
):
if not hasattr(objectspace.space, 'variables'):
if not objectspace.paths:
return
self.objectspace = objectspace
self.forbidden_name = ['services', self.objectspace.rougailconfig['variable_namespace']]
@ -149,109 +107,48 @@ class Annotator(Walk): # pylint: disable=R0903
"""convert variable
"""
for variable in self.get_variables():
if variable.type == 'symlink':
continue
self._convert_variable(variable)
def _convert_variable(self,
variable,
variable: dict,
) -> None:
if variable.namespace == self.objectspace.rougailconfig['variable_namespace'] and \
variable.name in self.forbidden_name:
msg = _(f'the name of the variable "{variable.name}" cannot be the same as the name'
' of a namespace')
raise DictConsistencyError(msg, 54, variable.xmlfiles)
if variable.type != 'symlink' and not hasattr(variable, 'description'):
# variable without description: description is the name
if not variable.description:
variable.description = variable.name
if hasattr(variable, 'value'):
for idx, value in enumerate(variable.value):
if not hasattr(value, 'name') and not hasattr(value, 'type'):
msg = f'variable "{variable.name}" has a value without text, if you want the value None, set value type to nil'
raise DictConsistencyError(msg, 95, value.xmlfiles)
if not hasattr(value, 'type'):
value.type = variable.type
if hasattr(value, 'name'):
try:
value.name = CONVERT_OPTION.get(value.type, {}).get('func', str)(value.name)
except Exception as err:
msg = _(f'the variable "{variable.name}" has an incorrect value "{value.name}" with "{variable.type}" type')
raise DictConsistencyError(msg, 88, variable.xmlfiles)
else:
value.name = None
if not variable.value:
del variable.value
if hasattr(variable, 'choice'):
if variable.type != 'choice':
msg = _(f'choice for the variable "{variable.name}" not allowed with "{variable.type}" type')
raise DictConsistencyError(msg, 3, variable.xmlfiles)
values = []
choice_type = None
for choice in variable.choice:
if choice_type == 'variable':
msg = _(f'only one "variable" choice is allowed '
f'the variable "{variable.name}"')
raise DictConsistencyError(msg, 5, choice.xmlfiles)
if choice.type == 'nil':
choice.name = None
elif choice.type == 'space':
choice.name = ' '
elif choice.type == 'variable':
choice.name = self.objectspace.paths.get_variable(choice.name,
variable.namespace,
force_path_prefix=variable.path_prefix,
)
if not choice.name.multi:
msg = _(f'only multi "variable" is allowed for a choice '
f'of variable "{variable.name}"')
raise DictConsistencyError(msg, 6, choice.xmlfiles)
else:
if not hasattr(choice, 'name'):
msg = _(f'choice for variable "{variable.name}" must have a value')
raise DictConsistencyError(msg, 14, choice.xmlfiles)
choice.name = CONVERT_OPTION.get(choice.type, {}).get('func', str)(choice.name)
if choice_type is None:
choice_type = choice.type
values.append(choice.name)
if choice_type not in ['function', 'variable'] and hasattr(variable, 'value'):
for value in variable.value:
if value.name not in values:
msg = _(f'value "{value.name}" of variable "{variable.name}" is not in list '
f'of all expected values ({values})')
raise DictConsistencyError(msg, 15, value.xmlfiles)
ref_choice = variable.choice[0]
self.objectspace.paths.set_valid_enums(variable.path,
values,
variable.path_prefix,
)
elif variable.type == 'choice':
msg = _(f'choice is mandatory for the variable "{variable.name}" with choice type')
raise DictConsistencyError(msg, 4, variable.xmlfiles)
variable.doc = variable.description
del variable.description
if variable.path in self.objectspace.followers:
if not variable.multi:
self.objectspace.multis[variable.path] = True
else:
self.objectspace.multis[variable.path] = 'submulti'
elif variable.multi:
self.objectspace.multis[variable.path] = True
if variable.path in self.objectspace.leaders:
if not self.objectspace.multis.get(variable.path, False):
msg = _(f'the variable "{variable.name}" in a leadership must be multi')
raise DictConsistencyError(msg, 32, variable.xmlfiles)
family = self.objectspace.paths[variable.path.rsplit('.', 1)[0]]
if variable.hidden:
family.hidden = variable.hidden
elif family.hidden:
variable.hidden = family.hidden
variable.hidden = None
def convert_test(self):
"""Convert variable tests value
"""
for variable in self.get_variables():
if not hasattr(variable, 'test') or not variable.test:
if variable.test is None:
# with we want remove test, we set "" has test value
continue
new_values = []
for value in variable.test.split('|'):
if value == '':
value = None
else:
value = CONVERT_OPTION.get(variable.type, {}).get('func', str)(value)
new_values.append(value)
if not hasattr(variable, 'information'):
variable.information = self.objectspace.information(variable.xmlfiles)
variable.information.test = tuple(new_values)
self.objectspace.informations.add(variable.path, 'test', tuple(variable.test))
def convert_help(self):
"""Convert variable help
"""
for variable in self.get_variables():
if not hasattr(variable, 'help'):
if not hasattr(variable, 'help') or not variable.help:
continue
if not hasattr(variable, 'information'):
variable.information = self.objectspace.information(variable.xmlfiles)
variable.information.help = variable.help
self.objectspace.informations.add(variable.path, 'help', variable.help)
del variable.help

View file

@ -37,6 +37,7 @@ DTDDIR = join(dirname(abspath(__file__)), 'data')
RougailConfig = {'dictionaries_dir': [join(ROUGAILROOT, 'dictionaries')],
'extra_dictionaries': {},
'services_dir': [join(ROUGAILROOT, 'services')],
'patches_dir': join(ROUGAILROOT, 'patches'),
'templates_dir': join(ROUGAILROOT, 'templates'),
'destinations_dir': join(ROUGAILROOT, 'destinations'),
@ -62,16 +63,17 @@ RougailConfig = {'dictionaries_dir': [join(ROUGAILROOT, 'dictionaries')],
'modes_level': ['basic', 'normal', 'expert'],
'default_family_mode': 'basic',
'default_variable_mode': 'normal',
'default_files_engine': 'cheetah',
'default_files_engine': 'jinja',
'default_files_mode': 644,
'default_files_owner': 'root',
'default_files_group': 'root',
'default_files_included': 'no',
'default_overrides_engine': 'cheetah',
'default_overrides_engine': 'jinja',
'default_service_names_engine': 'none',
'default_certificate_domain': 'server_name',
'default_certificate_domain': 'rougail.server_name',
'base_option_name': 'baseoption',
'export_with_import': True,
'force_convert_dyn_option_description': False,
'suffix': '',
'tiramisu_cache': None,
}

View file

@ -43,115 +43,794 @@ The Rougail
The visit/annotation stage is a complex step that corresponds to the Rougail
procedures.
"""
from typing import List
from tiramisu import Config
from pathlib import Path
from typing import Optional, Union, get_type_hints, Any, Literal, List, Dict, Iterator
from yaml import safe_load
from pydantic.error_wrappers import ValidationError
import logging
from .i18n import _
from .config import RougailConfig
from .objspace import RougailObjSpace
from .reflector import Reflector
from .tiramisureflector import TiramisuReflector
from .annotator import SpaceAnnotator
from .tiramisureflector import TiramisuReflector
from .utils import get_realpath
from .object_model import Family, Dynamic, Variable, Choice, SymLink, \
CALCULATION_TYPES, Calculation, PARAM_TYPES, AnyParam
from .error import DictConsistencyError
from .providersupplier import provider_supplier
from .utils import normalize_family
class RougailConvert:
"""Rougail object
"""
property_types = Union[Literal[True], Calculation]
properties_types = Dict[str, property_types]
class Property:
def __init__(self) -> None:
self._properties: Dict[str, properties_types] = {}
def add(self,
path: str,
property_: str,
value: property_types,
) -> None:
self._properties.setdefault(path, {})[property_] = value
def __getitem__(self,
path: str,
) -> properties_types:
return self._properties.get(path, {})
def __contains__(self,
path: str,
) -> bool:
return path in self._properties
class Paths:
def __init__(self) -> None:
self._data: Dict[str, Union[Variable, Family]] = {}
self._dynamics: List[str] = []
self.path_prefix = None
def has_value(self) -> bool:
return self._data != {}
def add(self,
path: str,
data: Any,
is_dynamic: bool,
force: bool=False,
) -> None:
self._data[path] = data
if not force and is_dynamic:
self._dynamics.append(path)
def get_with_dynamic(self,
path: str,
) -> Any:
suffix = None
dynamic_path = None
if not path in self._data:
for dynamic in self._dynamics:
if path.startswith(dynamic):
subpaths = path[len(dynamic):].split('.')
if len(subpaths) > 1 and subpaths[0]:
dynamic_path = dynamic
suffix = subpaths[0]
len_suffix = len(suffix)
for subpath in subpaths[1:]:
if not subpath.endswith(suffix):
suffix = None
break
dynamic_path += '.' + subpath[:-len_suffix]
if dynamic_path not in self._dynamics:
suffix = None
break
if suffix:
break
if suffix is None and not path in self._data:
return None, None
if suffix and dynamic_path:
path = dynamic_path
return self._data[path], suffix
def __getitem__(self,
path: str,
) -> Union[Family, Variable]:
if not path in self._data:
raise Exception(f'cannot find variable or family {path}')
return self._data[path]
def __contains__(self,
path: str,
) -> bool:
return path in self._data
def __delitem__(self,
path: str,
) -> None:
logging.info('remove empty family %s', path)
del self._data[path]
def is_dynamic(self, path: str) -> bool:
return path in self._dynamics
def get(self):
return self._data.values()
information_types = Dict[str, Union[str, int, float, bool]]
class Informations:
def __init__(self) -> None:
self._data: Dict[str, information_types] = {}
def add(self,
path: str,
key: str,
data: Any,
) -> None:
if path not in self._data:
self._data[path] = {}
if key in self._data[path]:
raise Exception(f'already key {key} in {path}')
self._data[path][key] = data
def get(self,
path: str,
) -> information_types:
return self._data.get(path, {})
class ParserVariable:
def __init__(self):
self.paths = Paths()
self.families = []
self.variables = []
self.parents = {'.': []}
self.index = 0
self.reflector_names = {}
self.leaders = []
self.followers = []
self.multis = {}
self.default_multi = {}
self.jinja = {}
#
self.family = Family
self.dynamic = Dynamic
self.variable = Variable
self.choice = Choice
#FIXME
self.exclude_imports = []
self.informations = Informations()
self.properties = Property()
# self.choices = Appendable()
self.has_dyn_option = False
self.path_prefix = None
super().__init__()
def init(self):
hint = get_type_hints(self.dynamic)
self.family_types = hint['type'].__args__
self.family_attrs = frozenset(set(hint) | {'redefine'} - {'name', 'path', 'xmlfiles'})
self.family_calculations = self.search_calculation(hint)
#
hint= get_type_hints(self.variable)
self.variable_types = hint['type'].__args__
#
hint= get_type_hints(self.choice)
self.choice_attrs = frozenset(set(hint) | {'redefine', 'exists'} - {'name', 'path', 'xmlfiles'})
self.choice_calculations = self.search_calculation(hint)
###################################################################################################
# determine if the object is a family or a variable
###################################################################################################
def is_family_or_variable(self,
path: str,
obj: dict,
family_is_leadership: bool,
) -> Literal['variable', 'family']:
""" Check object to determine if it's a variable or a family
"""
# it's already has a variable or a family
if path in self.paths:
if path in self.families:
return 'family'
return 'variable'
# it's: "my_variable:"
if not obj:
return 'variable'
# check type attributes
obj_type = self.get_family_or_variable_type(obj)
if obj_type:
if obj_type in self.family_types:
return 'family'
if obj_type in self.variable_types:
return 'variable'
raise Exception(f'unknown type {obj_type} for {path}')
# in a leadership there is only variable
if family_is_leadership:
return 'variable'
# all attributes are in variable object
# and values in attributes are not dict is not Calculation
extra_keys = set(obj) - self.choice_attrs
if not extra_keys:
for key, value in obj.items():
if isinstance(value, dict) and not self.is_calculation(key,
value,
'variable',
False,
):
break
else:
return 'variable'
#FIXME do not valid # check all attributs not known in family
# for key in set(obj) - self.family_attrs:
# # family attribute can start with '_'
# if key.startswith('_') and key[1:] in self.family_attrs:
# continue
# value = obj[key]
# # a variable or a family is a dict (or None)
# if value is not None and not isinstance(value, dict):
# raise Exception(f'cannot determine if "{path}" is a variable or a family')
# default is family
return 'family'
def get_family_or_variable_type(self,
obj: dict,
) -> Optional[str]:
""" Check 'type' attributes
"""
if '_type' in obj:
# only family has _type attributs
return obj['_type']
if 'type' in obj and isinstance(obj['type'], str):
return obj['type']
return None
###################################################################################################
# create, update or delete family or variable object
###################################################################################################
def family_or_variable(self,
filename: str,
name: str,
subpath: str,
obj: dict,
first_variable: bool=False,
family_is_leadership: bool=False,
family_is_dynamic: bool=False,
) -> None:
if name.startswith('_'):
raise Exception('forbidden!')
path = f'{subpath}.{name}'
typ = self.is_family_or_variable(path,
obj,
family_is_leadership,
)
logging.info('family_or_variable: %s is a %s', path, typ)
if typ == 'family':
parser = self.parse_family
else:
parser = self.parse_variable
parser(filename,
name,
path,
obj,
first_variable,
family_is_leadership,
family_is_dynamic,
)
def parse_family(self,
filename: str,
name: str,
path: str,
obj: Optional[Dict[str, Any]],
first_variable: bool=False,
family_is_leadership: bool=False,
family_is_dynamic: bool=False,
) -> None:
if obj is None:
return
family_obj = {}
subfamily_obj = {}
force_to_attrs = list(self.list_attributes(obj))
for key, value in obj.items():
if key in force_to_attrs:
if key.startswith('_'):
key = key[1:]
family_obj[key] = value
else:
subfamily_obj[key] = value
if path in self.paths:
if family_obj:
if not obj.pop('redefine', False):
raise Exception('pfff')
self.paths.add(path,
self.paths[path].copy(update=obj),
family_is_dynamic,
force=True,
)
self.paths[path].xmlfiles.append(filename)
force_not_first = True
if self.paths[path].type == 'dynamic':
family_is_dynamic = True
else:
if 'redefine' in obj and obj['redefine']:
raise Exception(f'cannot redefine the inexisting family "{path}" in {filename}')
extra_attrs = set(family_obj) - self.family_attrs
if extra_attrs:
raise Exception(f'extra attrs ... {extra_attrs}')
if self.get_family_or_variable_type(family_obj) == 'dynamic':
family_is_dynamic = True
self.add_family(path,
name,
family_obj,
filename,
family_is_dynamic,
)
force_not_first = False
if self.paths[path].type == 'leadership':
family_is_leadership = True
for idx, key in enumerate(subfamily_obj):
value = subfamily_obj[key]
if not isinstance(value, dict) and value is not None:
raise Exception(f'pfff {key}')
first_variable = not force_not_first and idx == 0
if value is None:
value = {}
self.family_or_variable(filename,
key,
path,
value,
first_variable,
family_is_leadership,
family_is_dynamic,
)
def list_attributes(self,
obj: Dict[str, Any],
) -> Iterator[str]:
force_to_variable = []
for key, value in obj.items():
if key in force_to_variable:
continue
if key.startswith('_'):
# if key starts with _, it's an attribute
yield key
# if same key without _ exists, it's a variable!
true_key = key[1:]
if true_key in obj:
force_to_variable.append(true_key)
continue
if isinstance(value, dict) and not self.is_calculation(key,
value,
'family',
False,
):
# it's a dict, so a new variables!
continue
if key in self.family_attrs:
yield key
def add_family(self,
path: str,
name: str,
family: dict,
filenames: Union[str, List[str]],
family_is_dynamic: bool,
) -> None:
family['path'] = path
if not isinstance(filenames, list):
filenames = [filenames]
family['xmlfiles'] = filenames
obj_type = self.get_family_or_variable_type(family)
if obj_type == 'dynamic':
family_obj = self.dynamic
if 'variable' in family:
family['variable'] = get_realpath(family['variable'],
self.path_prefix,
)
else:
family_obj = self.family
# convert to Calculation objects
for key, value in family.items():
if not self.is_calculation(key,
value,
'family',
False,
):
continue
try:
self.set_calculation(family,
key,
value,
path,
)
except ValidationError as err:
raise Exception(f'the family "{path}" in "{filenames}" has an invalid "{key}": {err}')
try:
self.paths.add(path,
family_obj(name=name, **family),
family_is_dynamic,
)
except ValidationError as err:
raise Exception(f'invalid family "{path}" in "{filenames}": {err}')
self.set_name(self.paths[path],
'optiondescription_',
)
if '.' not in path:
parent = '.'
else:
parent = path.rsplit('.', 1)[0]
self.parents[parent].append(path)
self.parents[path] = []
self.families.append(path)
def parse_variable(self,
filename: str,
name: str,
path: str,
obj: Optional[Dict[str, Any]],
first_variable: bool=False,
family_is_leadership: bool=False,
family_is_dynamic: bool=False,
) -> None:
#print(path)
if obj is None:
obj = {}
extra_attrs = set(obj) - self.choice_attrs
if extra_attrs:
raise Exception(f'"{path}" is not a valid variable, there are additional attributes: "{", ".join(extra_attrs)}"')
# convert to Calculation objects
for key, value in obj.items():
if self.is_calculation(key,
value,
'variable',
False,
):
try:
self.set_calculation(obj,
key,
value,
path,
)
except ValidationError as err:
raise Exception(f'the variable "{path}" in "{filename}" has an invalid "{key}": {err}')
continue
if not isinstance(value, list) or key not in self.choice_calculations[0]:
continue
for idx, val in enumerate(value):
if not self.is_calculation(key,
val,
'variable',
True,
):
continue
try:
self.set_calculation(obj,
key,
val,
path,
inside_list=True,
index=idx,
)
except ValidationError as err:
raise Exception(f'the variable "{path}" in "{filename}" has an invalid "{key}" at index {idx}: {err}')
if 'params' in obj:
params = []
for key, val in obj['params'].items():
try:
params.append(AnyParam(key=key, value=val, type='any'))
except ValidationError as err:
raise Exception(f'"{key}" has an invalid "params" for {path}: {err}')
obj['params'] = params
if path in self.paths:
if 'exists' in obj and not obj.pop('exists'):
return
if not obj.pop('redefine', False):
raise Exception(f'Variable "{path}" already exists')
self.paths.add(path, self.paths[path].copy(update=obj), False, force=True)
self.paths[path].xmlfiles.append(filename)
else:
if 'exists' in obj and obj.pop('exists'):
# this variable must exist
# but it's not the case
# so do nothing
return
if 'redefine' in obj and obj['redefine']:
raise Exception(f'cannot redefine the inexisting variable "{path}" in {filename}')
self.add_variable(path,
name,
obj,
filename,
family_is_dynamic,
)
if family_is_leadership:
if first_variable:
self.leaders.append(path)
else:
self.followers.append(path)
def add_variable(self,
path: str,
name: str,
variable: dict,
filename: str,
family_is_dynamic: bool,
) -> None:
variable['path'] = path
if not isinstance(filename, list):
filename = [filename]
variable['xmlfiles'] = filename
try:
if self.get_family_or_variable_type(variable) == 'symlink':
variable_obj = SymLink(name=name, **variable)
elif self.get_family_or_variable_type(variable) == 'choice':
variable_obj = self.choice(name=name, **variable)
else:
variable_obj = self.variable(name=name, **variable)
except ValidationError as err:
raise Exception(f'invalid variable "{path}" in "{filename}": {err}')
self.paths.add(path,
variable_obj,
family_is_dynamic,
)
self.variables.append(path)
self.parents[path.rsplit('.', 1)[0]].append(path)
self.set_name(variable_obj,
'option_',
)
def del_family(self,
path: str,
) -> None:
del self.paths[path]
self.families.remove(path)
del self.parents[path]
parent = path.rsplit('.', 1)[0]
self.parents[parent].remove(path)
###################################################################################################
# set tiramisu file name
###################################################################################################
def set_name(self,
obj: Union[Variable, Family],
option_prefix: str,
):
self.index += 1
self.reflector_names[obj.path] = f'{option_prefix}{self.index}{self.rougailconfig["suffix"]}'
###################################################################################################
# calculations
###################################################################################################
def is_calculation(self,
attribute: str,
value: dict,
typ: Literal['variable', 'family'],
inside_list: bool,
):
if typ == 'variable':
calculations = self.choice_calculations
else:
calculations = self.family_calculations
if inside_list:
calculations = calculations[0]
else:
calculations = calculations[1]
return attribute in calculations and \
isinstance(value, dict) and \
value.get('type') in CALCULATION_TYPES
def set_calculation(self,
obj: dict,
attribute: str,
value: dict,
path: str,
*,
inside_list: bool=False,
index: int=None,
):
calculation_object = value.copy()
typ = calculation_object.pop('type')
calculation_object['attribute_name'] = attribute
calculation_object['path_prefix'] = self.path_prefix
calculation_object['path'] = path
calculation_object['inside_list'] = inside_list
#
if 'params' in calculation_object:
if not isinstance(calculation_object['params'], dict):
raise Exception('params must be a dict')
params = []
for key, val in calculation_object['params'].items():
if not isinstance(val, dict) or 'type' not in val:
param_typ = 'any'
val = {'value': val,
'type': 'any',
}
else:
param_typ = val['type']
val['key'] = key
try:
params.append(PARAM_TYPES[param_typ](**val))
except ValidationError as err:
raise Exception(f'"{attribute}" has an invalid "{key}" for {path}: {err}')
calculation_object['params'] = params
#
return_type = calculation_object.get('return_type')
if return_type:
if return_type not in self.variable_types:
raise Exception(f'unknown "return_type" in {attribute} of variable "{path}"')
#
if index is None:
obj[attribute] = CALCULATION_TYPES[typ](**calculation_object)
if index is not None:
obj[attribute][index] = CALCULATION_TYPES[typ](**calculation_object)
class RougailConvert(ParserVariable):
supported_version = ['1.0']
def __init__(self,
rougailconfig: RougailConfig=None,
just_doc: bool=False,
rougailconfig: 'RougailConfig'
) -> None:
if rougailconfig is None:
rougailconfig = RougailConfig
self.rougailconfig = rougailconfig
xmlreflector = Reflector(self.rougailconfig)
self.rougailobjspace = RougailObjSpace(xmlreflector,
self.rougailconfig,
just_doc,
)
self.internal_functions = self.rougailconfig['internal_functions']
self.dictionaries = False
# FIXME useful?
self.annotator = False
self.reflector = None
self.rougailconfig = rougailconfig
super().__init__()
self.is_init = False
def load_dictionaries(self,
path_prefix: str=None,
) -> None:
self.rougailobjspace.paths.set_path_prefix(normalize_family(path_prefix))
self._load_dictionaries(self.rougailobjspace.xmlreflector,
self.rougailconfig['variable_namespace'],
self.rougailconfig['dictionaries_dir'],
path_prefix,
self.rougailconfig['variable_namespace_description'],
)
for namespace, extra_dir in self.rougailconfig['extra_dictionaries'].items():
if namespace in ['services', self.rougailconfig['variable_namespace']]:
msg = _(f'Namespace name "{namespace}" is not allowed')
raise DictConsistencyError(msg, 21, None)
self._load_dictionaries(self.rougailobjspace.xmlreflector,
namespace,
extra_dir,
path_prefix,
)
if hasattr(self.rougailobjspace.space, 'variables'):
provider_supplier(self.rougailobjspace,
path_prefix,
)
self.dictionaries = True
def _load_dictionaries(self,
xmlreflector: Reflector,
namespace: str,
xmlfolders: List[str],
path_prefix: str,
namespace_description: str=None,
def search_calculation(self,
hint: dict,
) -> List[str]:
for xmlfile, document in xmlreflector.load_dictionaries_from_folders(xmlfolders, self.rougailobjspace.just_doc):
self.rougailobjspace.xml_parse_document(xmlfile,
document,
namespace,
namespace_description,
path_prefix,
)
""" attribute is calculated if typing is like: Union[Calculation, xxx]
"""
inside_list = []
outside_list = []
for key, value in hint.items():
if 'Union' in value.__class__.__name__ and Calculation in value.__args__:
outside_list.append(key)
if 'Union' in value.__class__.__name__ and \
'_GenericAlias' in value.__args__[0].__class__.__name__ and \
Calculation in value.__args__[0].__args__:
inside_list.append(key)
if 'Union' in value.__class__.__name__ and \
value.__args__[0].__class__.__name__ == '_GenericAlias' and \
'Union' in value.__args__[0].__args__[0].__class__.__name__ and \
Calculation in value.__args__[0].__args__[0].__args__:
inside_list.append(key)
return inside_list, outside_list
def parse_directories(self,
path_prefix: Optional[str]=None,
) -> None:
if not self.is_init:
self.init()
self.is_init = True
if path_prefix:
if path_prefix in self.parents:
raise Exception('pfffff')
root_parent = path_prefix
self.path_prefix = path_prefix
self.add_family(path_prefix,
path_prefix,
{},
'',
False,
)
else:
root_parent = '.'
namespace = self.rougailconfig['variable_namespace']
if root_parent == '.':
namespace_path = namespace
else:
namespace_path = f'{root_parent}.{namespace}'
if namespace_path in self.parents:
raise Exception('pfff')
for filename in self.get_sorted_filename(self.rougailconfig['dictionaries_dir']):
self.parse_variable_file(filename,
namespace,
namespace_path,
)
for namespace, extra_dirs in self.rougailconfig['extra_dictionaries'].items():
if root_parent == '.':
namespace_path = namespace
else:
namespace_path = f'{root_parent}.{namespace}'
if namespace_path in self.parents:
raise Exception('pfff')
#self.parents[root_parent].append(namespace_path)
#self.parents[namespace_path] = []
for filename in self.get_sorted_filename(extra_dirs):
self.parse_variable_file(filename,
namespace,
namespace_path,
)
if path_prefix:
self.path_prefix = None
#print(self.parents)
def parse_variable_file(self,
filename: str,
namespace: str,
path: str,
# description: str,
) -> None:
with open(filename) as o:
objects = safe_load(o)
self.validate_file_version(objects)
self.parse_family(filename,
namespace,
path,
{},
)
for name, obj in objects.items():
self.family_or_variable(filename,
name,
path,
obj,
)
def get_sorted_filename(self,
directories: Union[str, List[str]],
) -> List[str]:
if not isinstance(directories, list):
directories = [directories]
for directory in directories:
directory = Path(directory)
if not directory.is_dir():
continue
filenames = {}
for file_path in directory.iterdir():
if not file_path.suffix == '.yml':
continue
# full_filename = directory / Path(filename)
if file_path.name in filenames:
raise DictConsistencyError(_(f'duplicate dictionary file name {file_path.name}'), 78, [filenames[file_path.name][1], full_filename])
filenames[file_path.name] = str(file_path)
for filename in sorted(filenames):
yield filenames[filename]
def validate_file_version(self,
obj: dict,
) -> None:
if 'version' not in obj:
raise Exception('version ...')
version = obj.pop('version')
if version not in self.supported_version:
raise Exception(f'pffff version ... {version} not in {self.supported_version}')
def annotate(self):
if not self.paths.has_value():
self.parse_directories()
if self.annotator:
raise DictConsistencyError(_('Cannot execute annotate multiple time'), 85, None)
SpaceAnnotator(self.rougailobjspace)
SpaceAnnotator(self)
self.annotator = True
def reflexion(self,
exclude_imports: list=[],
):
if not self.dictionaries:
self.load_dictionaries()
if not self.annotator:
self.annotate()
if self.reflector:
raise DictConsistencyError(_('Cannot execute reflexion multiple time'), 86, None)
def reflect(self) -> None:
"""Apply TiramisuReflector
"""
functions_file = self.rougailconfig['functions_file']
if not isinstance(functions_file, list):
functions_file = [functions_file]
functions_file = [func for func in functions_file if func not in exclude_imports]
self.reflector = TiramisuReflector(self.rougailobjspace,
functions_file = [func for func in functions_file if func not in self.exclude_imports]
self.reflector = TiramisuReflector(self,
functions_file,
self.internal_functions,
self.rougailconfig,
)
def save(self,
filename: str,
) -> str:
filename: None,
) -> 'OptionDescription':
"""Return tiramisu object declaration as a string
"""
if self.reflector is None:
self.reflexion()
self.annotate()
self.reflect()
output = self.reflector.get_text() + '\n'
if filename:
with open(filename, 'w') as tiramisu:
with open(filename, 'w', encoding="utf-8") as tiramisu:
tiramisu.write(output)
# print(output)
return output

View file

@ -1,176 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- ===================================================================== -->
<!-- Rougail's DTD -->
<!-- ===================================================================== -->
<!--
# Created by:
# EOLE (http://eole.orion.education.fr)
# Copyright (C) 2005-2018
# Forked by:
# Cadoles (http://www.cadoles.com)
# Copyright (C) 2019-2021
# Silique (https://www.silique.fr)
# Copyright (C) 2022-2023
# distribued with GPL-2 or later license
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-->
<!--================ -->
<!-- root element -->
<!-- =============== -->
<!ELEMENT rougail (services|variables|constraints)*>
<!ATTLIST rougail version (0.10) #REQUIRED>
<!-- ============== -->
<!-- files element -->
<!-- ============== -->
<!ELEMENT services (service*)>
<!ELEMENT service ((ip*|file*|override*|certificate*)*)>
<!ATTLIST service name CDATA #REQUIRED>
<!ATTLIST service manage (True|False) "True">
<!ATTLIST service servicelist CDATA #IMPLIED>
<!ATTLIST service disabled (True|False) "False">
<!ATTLIST service engine CDATA #IMPLIED>
<!ATTLIST service target CDATA #IMPLIED>
<!ATTLIST service type (service|mount|swap|timer|target) "service">
<!ATTLIST service undisable (True|False) "False">
<!ELEMENT ip (#PCDATA)>
<!ATTLIST ip iplist CDATA #IMPLIED>
<!ATTLIST ip ip_type (variable) "variable">
<!ATTLIST ip netmask_type (variable) "variable">
<!ATTLIST ip netmask CDATA #IMPLIED>
<!ELEMENT file (#PCDATA)>
<!ATTLIST file file_type (filename|variable) "filename">
<!ATTLIST file variable CDATA #IMPLIED>
<!ATTLIST file variable_type (variable) "variable">
<!ATTLIST file source CDATA #IMPLIED>
<!ATTLIST file source_type (string|variable) "string">
<!ATTLIST file mode_type (unix_permissions) "unix_permissions">
<!ATTLIST file mode CDATA #IMPLIED>
<!ATTLIST file owner CDATA #IMPLIED>
<!ATTLIST file owner_type (unix_user|variable) "unix_user">
<!ATTLIST file group CDATA #IMPLIED>
<!ATTLIST file group_type (unix_user|variable) "unix_user">
<!ATTLIST file filelist CDATA #IMPLIED>
<!ATTLIST file redefine (True|False) "False">
<!ATTLIST file engine CDATA #IMPLIED>
<!ATTLIST file included (no|name|content) #IMPLIED>
<!ATTLIST file disabled (True|False) "False">
<!ELEMENT override EMPTY>
<!ATTLIST override source CDATA #IMPLIED>
<!ATTLIST override engine CDATA #IMPLIED>
<!ELEMENT certificate (#PCDATA)>
<!ATTLIST certificate certificate_type (string|variable) "string">
<!ATTLIST certificate authority CDATA #REQUIRED>
<!ATTLIST certificate owner CDATA #IMPLIED>
<!ATTLIST certificate owner_type (unix_user|variable) "unix_user">
<!ATTLIST certificate group CDATA #IMPLIED>
<!ATTLIST certificate group_type (unix_user|variable) "unix_user">
<!ATTLIST certificate server CDATA #IMPLIED>
<!ATTLIST certificate server_type (variable) "variable">
<!ATTLIST certificate domain CDATA #IMPLIED>
<!ATTLIST certificate domain_type (variable) "variable">
<!ATTLIST certificate provider CDATA #IMPLIED>
<!ATTLIST certificate provider_type (variable) "variable">
<!ATTLIST certificate format (cert_key|pem) "cert_key">
<!ATTLIST certificate type (client|server) "client">
<!ATTLIST certificate redefine (True|False) "False">
<!ATTLIST certificate certificatelist CDATA #IMPLIED>
<!ELEMENT variables ((variable*|family*)*)>
<!ELEMENT family ((variable*|family*)*)>
<!ATTLIST family name CDATA #REQUIRED>
<!ATTLIST family description CDATA #IMPLIED>
<!ATTLIST family help CDATA #IMPLIED>
<!ATTLIST family mode CDATA #IMPLIED>
<!ATTLIST family hidden (True|False) "False">
<!ATTLIST family disabled (True|False) "False">
<!ATTLIST family dynamic CDATA #IMPLIED>
<!ATTLIST family leadership (True|False) "False">
<!ELEMENT variable ((choice*|value*)*)>
<!ATTLIST variable name CDATA #REQUIRED>
<!ATTLIST variable type (number|float|string|password|secret|mail|boolean|filename|date|unix_user|ip|local_ip|netmask|network|broadcast|netbios|domainname|hostname|web_address|port|mac|cidr|network_cidr|choice|unix_permissions) "string">
<!ATTLIST variable description CDATA #IMPLIED>
<!ATTLIST variable help CDATA #IMPLIED>
<!ATTLIST variable hidden (True|False) "False">
<!ATTLIST variable disabled (True|False) "False">
<!ATTLIST variable multi (True|False) "False">
<!ATTLIST variable unique (nil|True|False) "nil">
<!ATTLIST variable redefine (True|False) "False">
<!ATTLIST variable exists (True|False) "True">
<!ATTLIST variable mandatory (True|False) "False">
<!ATTLIST variable auto_freeze (True|False) "False">
<!ATTLIST variable auto_save (True|False) "False">
<!ATTLIST variable mode CDATA #IMPLIED>
<!ATTLIST variable remove_choice (True|False) "False">
<!ATTLIST variable remove_check (True|False) "False">
<!ATTLIST variable remove_condition (True|False) "False">
<!ATTLIST variable remove_fill (True|False) "False">
<!ATTLIST variable provider CDATA #IMPLIED>
<!ATTLIST variable supplier CDATA #IMPLIED>
<!ATTLIST variable test CDATA #IMPLIED>
<!ELEMENT value (#PCDATA)>
<!ATTLIST value type (string|number|nil|space|boolean) #IMPLIED>
<!ELEMENT choice (#PCDATA | param)*>
<!ATTLIST choice type (string|number|nil|space|boolean|function|variable) "string">
<!ATTLIST choice name CDATA #IMPLIED>
<!ELEMENT constraints ((fill*|check*|condition*)*)>
<!ELEMENT fill ((target|param)+)>
<!ATTLIST fill name CDATA #REQUIRED>
<!ATTLIST fill type (function|jinja) "function">
<!ELEMENT check ((target|param)+)>
<!ATTLIST check name CDATA #REQUIRED>
<!ATTLIST check level (error|warning) "error">
<!ATTLIST check type (function|jinja) "function">
<!ELEMENT condition ((target|param)+)>
<!ATTLIST condition name (disabled_if_in|disabled_if_not_in|hidden_if_in|hidden_if_not_in|mandatory_if_in|mandatory_if_not_in) #REQUIRED>
<!ATTLIST condition source CDATA #REQUIRED>
<!ATTLIST condition optional (True|False) "False">
<!ATTLIST condition apply_on_fallback (True|False) #IMPLIED>
<!ELEMENT param (#PCDATA)>
<!ATTLIST param type (string|number|nil|space|boolean|variable|function|information|suffix|index) "string">
<!ATTLIST param name CDATA #IMPLIED>
<!ATTLIST param variable CDATA #IMPLIED>
<!ATTLIST param propertyerror (True|False) "True">
<!ATTLIST param optional (True|False) "False">
<!ELEMENT target (#PCDATA)>
<!ATTLIST target type (variable|family|servicelist|filelist|iplist|certificatelist) "variable">
<!ATTLIST target optional (True|False) "False">

File diff suppressed because it is too large Load diff

356
src/rougail/object_model.py Normal file
View file

@ -0,0 +1,356 @@
"""Rougail object model
Silique (https://www.silique.fr)
Copyright (C) 2023
distribued with GPL-2 or later license
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from typing import Optional, Union, get_type_hints, Any, Literal, List, Dict, Iterator
from pydantic import BaseModel, StrictBool, StrictInt, StrictFloat, StrictStr
from .utils import get_jinja_variable_to_param, get_realpath
BASETYPE = Union[StrictBool, StrictInt, StrictFloat, StrictStr, None]
class Param(BaseModel):
key: str
class AnyParam(Param):
type: str
value: BASETYPE
class VariableParam(Param):
type: str
variable: str
propertyerror: bool=True
optional: bool=False
class SuffixParam(Param):
type: str
class InformationParam(Param):
type: str
information: str
variable: Optional[str]=None
class IndexParam(Param):
type: str
PARAM_TYPES = {'any': AnyParam,
'variable': VariableParam,
'suffix': SuffixParam,
'information': InformationParam,
'index': IndexParam,
}
class Calculation(BaseModel):
path_prefix: Optional[str]
path: str
def get_realpath(self,
path: str,
) -> str:
return get_realpath(path, self.path_prefix)
def get_params(self, objectspace):
if not self.params:
return {}
params = {}
for param_obj in self.params:
param = param_obj.dict()
if param.get('type') == 'variable':
variable_path = self.get_realpath(param['variable'])
variable, suffix = objectspace.paths.get_with_dynamic(variable_path)
if not variable:
if not param.get('optional'):
raise Exception(f'pffff {variable_path}')
continue
if not isinstance(variable, objectspace.variable):
raise Exception("pfff it's a family")
param['variable'] = variable
if suffix:
param['suffix'] = suffix
if param.get('type') == 'information':
if param['variable']:
variable_path = self.get_realpath(param['variable'])
param['variable'] = objectspace.paths[variable_path]
if not param['variable']:
raise Exception('pffff')
else:
del param['variable']
params[param.pop('key')] = param
return params
class JinjaCalculation(Calculation):
attribute_name: Literal['frozen', 'hidden', 'mandatory', 'disabled', 'default', 'validators', 'choices']
jinja: StrictStr
params: Optional[List[Param]]
return_type: BASETYPE=None
inside_list: bool
def _jinja_to_function(self,
function,
return_type,
multi,
objectspace,
*,
add_help=False,
params: Optional[dict]=None,
):
variable = objectspace.paths[self.path]
jinja_path = f'{self.attribute_name}_{self.path}'
idx = 0
while jinja_path in objectspace.jinja:
jinja_path = f'{self.attribute_name}_{self.path}_{idx}'
idx += 1
objectspace.jinja[jinja_path] = self.jinja
default = {'function': function,
'params': {'__internal_jinja': jinja_path,
'__internal_type': return_type,
'__internal_multi': multi,
}
}
if add_help:
default['help'] = function + '_help'
if self.params:
default['params'] |= self.get_params(objectspace)
if params:
default['params'] |= params
for sub_variable, suffix, true_path in get_jinja_variable_to_param(self.jinja,
objectspace,
variable.xmlfiles,
objectspace.functions,
self.path_prefix,
):
if isinstance(sub_variable, objectspace.variable):
default['params'][true_path] = {'type': 'variable',
'variable': sub_variable,
}
if suffix:
default['params'][true_path]['suffix'] = suffix
return default
def to_function(self,
objectspace,
) -> dict:
if self.attribute_name == 'default':
if self.return_type:
raise Exception('return_type not allowed!')
variable = objectspace.paths[self.path]
return_type = variable.type
if self.inside_list:
multi = False
elif self.path in objectspace.followers:
multi = objectspace.multis[self.path] == 'submulti'
else:
multi = self.path in objectspace.multis
return self._jinja_to_function('jinja_to_function',
return_type,
multi,
objectspace,
)
elif self.attribute_name == 'validators':
if self.return_type:
raise Exception('pfff')
return self._jinja_to_function('valid_with_jinja',
'string',
False,
objectspace,
)
elif self.attribute_name in ['frozen', 'hidden', 'disabled', 'mandatory']:
if self.return_type:
raise Exception('return_type not allowed!')
return self._jinja_to_function('jinja_to_property',
'string',
False,
objectspace,
add_help=True,
params={None: [self.attribute_name]},
)
elif self.attribute_name == 'choices':
return_type = self.return_type
if return_type is None:
return_type = 'string'
return self._jinja_to_function('jinja_to_function',
return_type,
not self.inside_list,
objectspace,
)
raise Exception('hu?')
class VariableCalculation(Calculation):
attribute_name: Literal['frozen', 'hidden', 'mandatory', 'disabled', 'default', 'choices']
variable: StrictStr
propertyerror: bool=True
inside_list: bool
def to_function(self,
objectspace,
) -> dict:
variable_path = self.get_realpath(self.variable)
variable, suffix = objectspace.paths.get_with_dynamic(variable_path)
if not variable:
raise Exception(f'pffff {variable_path}')
if not isinstance(variable, objectspace.variable):
raise Exception("pfff it's a family")
param = {'type': 'variable',
'variable': variable,
'propertyerror': self.propertyerror,
}
if suffix:
param['suffix'] = suffix
params = {None: [param]}
function = 'calc_value'
help_function = None
if self.attribute_name in ['frozen', 'hidden', 'disabled', 'mandatory']:
function = 'variable_to_property'
help_function = 'variable_to_property'
if variable.type != 'boolean':
raise Exception('only boolean!')
params[None].insert(0, self.attribute_name)
elif self.attribute_name != 'default' and variable.path not in objectspace.multis:
raise Exception('pffff')
if not self.inside_list and self.path in objectspace.multis:
if not objectspace.paths.is_dynamic(variable_path) and \
variable_path not in objectspace.multis:
params['multi'] = True
params['allow_none'] = True
if self.inside_list and variable.path in objectspace.multis:
raise Exception('pfff')
ret = {'function': function,
'params': params,
}
if help_function:
ret['help'] = help_function
return ret
class InformationCalculation(Calculation):
attribute_name: Literal['default']
information: StrictStr
variable: Optional[StrictStr]
inside_list: bool
def to_function(self,
objectspace,
) -> dict:
param = {'type': 'information',
'information': self.information,
}
if self.variable:
variable_path = self.get_realpath(self.variable)
variable = objectspace.paths[variable_path]
if variable is None:
raise Exception('pfff')
param['variable'] = variable
return {'function': 'calc_value',
'params': {None: [param]},
}
class SuffixCalculation(Calculation):
attribute_name: Literal['default']
def to_function(self,
objectspace,
) -> dict:
return {'function': 'calc_value',
'params': {None: [{'type': 'suffix'}]},
}
class IndexCalculation(Calculation):
attribute_name: Literal['default']
def to_function(self,
objectspace,
) -> dict:
return {'function': 'calc_value',
'params': {None: [{'type': 'index'}]},
}
CALCULATION_TYPES = {'jinja': JinjaCalculation,
'variable': VariableCalculation,
'information': InformationCalculation,
'suffix': SuffixCalculation,
'index': IndexCalculation,
}
BASETYPE_CALC = Union[StrictBool, StrictInt, StrictFloat, StrictStr, None, Calculation]
class Family(BaseModel):
name: str
description: Optional[str]=None
type: Literal['family', 'leadership', 'dynamic']='family'
help: Optional[str]=None
mode: Optional[str]=None
hidden: Union[bool, Calculation]=False
disabled: Union[bool, Calculation]=False
xmlfiles: List[str]=[]
path: str
class Config:
arbitrary_types_allowed = True
class Dynamic(Family):
variable: str
class Variable(BaseModel):
name: str
type: Literal['number', 'float', 'string', 'password', 'secret', 'mail', 'boolean', 'filename', 'date', 'unix_user', 'ip', 'local_ip', 'netmask', 'network', 'broadcast', 'netbios', 'domainname', 'hostname', 'web_address', 'port', 'mac', 'cidr', 'network_cidr', 'choice', 'unix_permissions']='string'
description: Optional[str]=None
default: Union[List[BASETYPE_CALC], BASETYPE_CALC]=None
params: Optional[List[Param]]=None
validators: Optional[List[Calculation]]=None
multi: bool=False
unique: Optional[bool]=None
help: Optional[str]=None
hidden: Union[bool, Calculation]=False
disabled: Union[bool, Calculation]=False
mandatory: Union[None, bool, Calculation]=None
auto_save: bool=False
mode: Optional[str]=None
test: Optional[list]=None
xmlfiles: List[str]=[]
path: str
class Config:
arbitrary_types_allowed = True
class Choice(Variable):
choices: Union[List[BASETYPE_CALC], Calculation]
class SymLink(BaseModel):
name: str
type: str='symlink'
opt: Variable
xmlfiles: List[str]=[]
path: str

View file

@ -92,9 +92,10 @@ def convert_boolean(value: str) -> bool:
"""
if isinstance(value, bool):
return value
if value == 'True':
value = value.lower()
if value == 'true':
return True
elif value == 'False':
elif value == 'false':
return False
raise Exception(f'unknown boolean value {value}')
@ -587,24 +588,24 @@ class RougailObjSpace:
getattr(space, child.tag).append(variableobj)
else:
setattr(space, child.tag, variableobj)
def get_variables(objectspace):
"""Iter all variables from the objectspace
"""
if not hasattr(objectspace.space, 'variables'):
return
for family in objectspace.space.variables.values():
yield from _get_variables(family, objectspace.family)
def _get_variables(family, family_type):
if hasattr(family, 'variable'):
for variable in family.variable.values():
if isinstance(variable, family_type):
yield from _get_variables(variable, family_type)
else:
yield variable
if hasattr(family, 'variables'):
for family in family.variables.values():
yield from _get_variables(family, family_type)
#
#
#def get_variables(objectspace):
# """Iter all variables from the objectspace
# """
# if not hasattr(objectspace.space, 'variables'):
# return
# for family in objectspace.space.variables.values():
# yield from _get_variables(family, objectspace.family)
#
#
#def _get_variables(family, family_type):
# if hasattr(family, 'variable'):
# for variable in family.variable.values():
# if isinstance(variable, family_type):
# yield from _get_variables(variable, family_type)
# else:
# yield variable
# if hasattr(family, 'variables'):
# for family in family.variables.values():
# yield from _get_variables(family, family_type)

View file

@ -16,26 +16,26 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from rougail.objspace import get_variables
from rougail.utils import normalize_family
def provider_supplier(objectspace,
path_prefix,
):
n_path_prefix = normalize_family(path_prefix)
for variable in get_variables(objectspace):
if variable.path_prefix != n_path_prefix:
continue
if hasattr(variable, 'provider'):
family_name, variable.name = variable.path.rsplit('.', 1)
objectspace.paths.set_provider(variable,
variable.name,
family_name,
)
if hasattr(variable, 'supplier'):
family_name, variable.name = variable.path.rsplit('.', 1)
objectspace.paths.set_supplier(variable,
variable.name,
family_name,
)
#from rougail.objspace import get_variables
#from rougail.utils import normalize_family
#
#
#def provider_supplier(objectspace,
# path_prefix,
# ):
# n_path_prefix = normalize_family(path_prefix)
# for variable in get_variables(objectspace):
# if variable.path_prefix != n_path_prefix:
# continue
# if hasattr(variable, 'provider'):
# family_name, variable.name = variable.path.rsplit('.', 1)
# objectspace.paths.set_provider(variable,
# variable.name,
# family_name,
# )
# if hasattr(variable, 'supplier'):
# family_name, variable.name = variable.path.rsplit('.', 1)
# objectspace.paths.set_supplier(variable,
# variable.name,
# family_name,
# )

View file

@ -1,676 +0,0 @@
"""Template langage for Rougail
Created by:
EOLE (http://eole.orion.education.fr)
Copyright (C) 2005-2018
Forked by:
Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2023
distribued with GPL-2 or later license
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from shutil import copy
import logging
from typing import Dict, Any, Union
from subprocess import call
from os import listdir, makedirs, getcwd, chdir, unlink, rmdir, chmod
from os.path import dirname, join, isfile, isdir, abspath
try:
from tiramisu4 import Config, undefined
from tiramisu4.api import TiramisuOption
from tiramisu4.error import PropertiesOptionError # pragma: no cover
except ModuleNotFoundError: # pragma: no cover
from tiramisu import Config, undefined
from tiramisu.api import TiramisuOption
from tiramisu.error import PropertiesOptionError
from ..config import RougailConfig
from ..error import FileNotFound, TemplateError
from ..i18n import _
from ..utils import load_modules
from . import engine as engines
ENGINES = {}
for engine in engines.__all__:
ENGINES[engine] = getattr(engines, engine)
log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())
INFORMATIONS = {'files': ['mode', 'engine', 'included'],
'overrides': ['name', 'source', 'engine'],
'service_names': ['doc', 'engine', 'type', 'target', 'undisable'],
'certificates': ['authority', 'format'],
}
DEFAULT = {'files': ['owner', 'group'],
'overrides': [],
}
class RougailLeaderIndex:
"""This object is create when access to a specified Index of the variable
"""
def __init__(self,
value,
follower,
index,
) -> None:
self._value = value
self._follower = follower
self._index = index
def __getattr__(self, name):
if name not in self._follower:
raise AttributeError(f'unable to find follower "{name}"')
value = self._follower[name]
if isinstance(value, PropertiesOptionError):
raise AttributeError(f'unable to access to follower "{name}": {value}')
return value
def __getitem__(self, name):
return self.__getattr__(name)
def __contains__(self, name):
if self._follower.__contains__(name):
value = self._follower[name]
return not isinstance(value, PropertiesOptionError)
return False
def __str__(self):
return str(self._value)
def __lt__(self, value):
return self._value.__lt__(value)
def __le__(self, value):
return self._value.__le__(value)
def __eq__(self, value):
return self._value.__eq__(value)
def __ne__(self, value):
return self._value.__ne__(value)
def __gt__(self, value):
return self._value.__gt__(value)
def __ge__(self, value):
return self._value >= value
def __add__(self, value):
return self._value.__add__(value)
def __radd__(self, value):
return value + self._value
class RougailLeader:
"""Implement access to leader and follower variable
For examples: %%leader, %%leader[0].follower1
"""
def __init__(self,
leader_name,
value,
) -> None:
self._value = value
self._follower = {leader_name: value}
def __getitem__(self, index):
"""Get a leader.follower at requested index.
"""
followers = {key: values[index] for key, values in self._follower.items()}
return RougailLeaderIndex(self._value[index],
followers,
index,
)
def __iter__(self):
"""Iterate over leader.follower.
Return synchronised value of leader.follower.
"""
for index in range(len(self._value)):
yield self.__getitem__(index)
def __len__(self):
return len(self._value)
def __contains__(self, value):
return self._value.__contains__(value)
def _add_follower(self,
config,
name: str,
path: str,
):
"""Add a new follower
"""
self._follower[name] = []
for index in range(len(self._value)):
try:
value = config.option(path, index).value.get()
except PropertiesOptionError as err:
value = err
self._follower[name].append(value)
def index(self, value):
return self._value.index(value)
def __str__(self):
followers_name = list(self._follower)
return f'RougailLeader({followers_name[0]}) => {followers_name[1:]}'
class RougailExtra:
"""Object that implement access to extra variable
For example %%extra1.family.variable
"""
def __init__(self,
name: str,
suboption: Dict,
path: str,
) -> None:
self._name = name
self._suboption = suboption
self._path = path
def __getattr__(self,
key: str,
) -> Any:
try:
return self._suboption[key]
except KeyError:
raise AttributeError(f'unable to find extra "{self._path}.{key}"')
def __getitem__(self,
key: str,
) -> Any:
return self.__getattr__(key)
def __iter__(self):
return iter(self._suboption.values())
def items(self):
return self._suboption.items()
def __str__(self):
return f'RougailExtra("{self._name}") => {self._suboption}'
def __contains__(self, value):
return self._suboption.__contains__(value)
class RougailBaseTemplate:
"""Engine to process Creole cheetah template
"""
def __init__(self, # pylint: disable=R0913
root: Union[Config, TiramisuOption],
rougailconfig: RougailConfig=None,
) -> None:
if rougailconfig is None:
rougailconfig = RougailConfig
self.root = root
self.destinations_dir = abspath(rougailconfig['destinations_dir'])
self.tmp_dir = abspath(rougailconfig['tmp_dir'])
self.templates_dir = []
templates_dir = rougailconfig['templates_dir']
if not isinstance(templates_dir, list):
templates_dir = [templates_dir]
for templ_dir in templates_dir:
self.templates_dir.append(abspath(templ_dir))
patches_dir = rougailconfig['patches_dir']
if not isinstance(patches_dir, list):
patches_dir = [patches_dir]
self.patches_dir = []
for p_dir in patches_dir:
self.patches_dir.append(abspath(p_dir))
eos = {}
functions_file = rougailconfig['functions_file']
if not isinstance(functions_file, list):
functions_file = [functions_file]
for function in functions_file:
if isfile(function):
eosfunc = load_modules(function)
for func in dir(eosfunc):
if not func.startswith('_'):
eos[func] = getattr(eosfunc, func)
self.eosfunc = eos
self.rougail_variables_dict = {}
self.rougailconfig = rougailconfig
self.log = log
self.engines = ENGINES
def patch_template(self,
filename: str,
templates_dir: str,
) -> None:
"""Apply patch to a template
"""
patch_cmd = ['patch', '-d', self.tmp_dir, '-N', '-p1', '-f']
patch_no_debug = ['-s', '-r', '-', '--backup-if-mismatch']
for patches_dir in self.patches_dir:
patch_file = join(patches_dir, f'{filename}.patch')
if isfile(patch_file):
self.log.info(_("Patching template '{filename}' with '{patch_file}'"))
ret = call(patch_cmd + patch_no_debug + ['-i', patch_file])
if ret: # pragma: no cover
patch_cmd_err = ' '.join(patch_cmd + ['-i', patch_file])
msg = _(f"Error applying patch: '{patch_file}'\n"
f"To reproduce and fix this error {patch_cmd_err}")
self.log.error(_(msg))
copy(join(templates_dir, filename), self.tmp_dir)
def prepare_template(self,
filename: str,
templates_dir: str,
) -> None:
"""Prepare template source file
"""
self.log.info(_("Copy template: '{filename}' -> '{self.tmp_dir}'"))
if not isdir(self.tmp_dir):
raise TemplateError(_(f'cannot find tmp_dir {self.tmp_dir}'))
copy(join(templates_dir, filename), self.tmp_dir)
self.patch_template(filename, templates_dir)
def _instance_file(self,
filevar: Dict,
type_: str,
service_name: str,
extra_variables: str,
) -> str:
"""Run templatisation on one file
"""
if 'variable' in filevar:
variable = filevar['variable']
else:
variable = None
filenames = filevar.get('name')
if not isinstance(filenames, list):
filenames = [filenames]
if variable and not isinstance(variable, list):
variable = [variable]
if not isdir(self.destinations_dir):
raise TemplateError(_(f'cannot find destinations_dir {self.destinations_dir}'))
destfilenames = []
for idx, filename, in enumerate(filenames):
if variable:
var = variable[idx]
else:
var = None
func = f'get_data_{type_}'
data = getattr(self, func)(filevar,
filename,
service_name,
variable,
idx,
)
if data is None:
continue
filename, source, true_destfilename, var = data
self.log.info(_(f'Instantiating file "{filename}"'))
if not true_destfilename.startswith('/'):
raise TemplateError(f'true_destfilename must starts with a / in function {func}')
destfilename = join(self.destinations_dir, true_destfilename[1:])
makedirs(dirname(destfilename), exist_ok=True)
self.log.info(_(f"{filevar['engine']} processing: '{destfilename}'"))
if isfile(destfilename):
raise TemplateError(_(f'destination file "{destfilename}" already exists'))
self.engines[filevar['engine']].process(filename=filename,
source=source,
true_destfilename=true_destfilename,
destfilename=destfilename,
destdir=self.destinations_dir,
variable=var,
index=idx,
rougail_variables_dict=self.rougail_variables_dict,
eosfunc=self.eosfunc,
extra_variables=extra_variables,
)
self.process(true_destfilename,
destfilename,
filevar.get('mode'),
filevar.get('owner'),
filevar.get('group'),
)
destfilenames.append(destfilename)
return destfilenames
def load_variables(self, with_flatten=True):
if isinstance(self.root, Config):
list_options = self.root.option.list(type='all')
else:
list_options = self.root.list(type='all')
for option in list_options:
namespace = option.name()
if with_flatten and namespace == self.rougailconfig['variable_namespace']:
is_variable_namespace = True
else:
is_variable_namespace = False
if namespace == 'services':
is_service_namespace = 'root'
else:
is_service_namespace = False
self.rougail_variables_dict[namespace] = self._load_variables(option,
is_variable_namespace,
is_service_namespace,
)
def instance_file(self,
template_name,
extra_variables=None,
) -> None:
if not self.rougail_variables_dict:
self.load_variables()
self.prepare_templates()
for service_obj in self.root.option('services').list('all'):
service_name = service_obj.description()
service_desactived = service_obj.option('activate').value.get() is False
for fills in service_obj.list('optiondescription'):
type_ = fills.name()
for fill_obj in fills.list('all'):
fill = {path.split('.')[-1]: value for path, value in fill_obj.value.dict().items()}
self.get_default(type_, fill, fill_obj)
self.get_informations(type_, fill, fill_obj)
if fill['source'] != template_name:
continue
if service_desactived:
raise TemplateError(f'template {template_name} is inside a desactived service')
if 'included' in fill and fill['included'] != 'no':
raise TemplateError(f'template {template_name} is an included file')
if not fill['activate']:
raise TemplateError(f'template {template_name} is desactived')
try:
ori_dir = getcwd()
except FileNotFoundError:
ori_dir = None
chdir(self.tmp_dir)
try:
self._instance_file(fill,
type_,
service_name,
extra_variables,
)
except Exception as err:
if ori_dir is not None:
chdir(ori_dir)
raise err from err
if ori_dir is not None:
chdir(ori_dir)
return
raise TemplateError(f'Cannot find template {template_name}')
def instance_files(self,
extra_variables=None,
) -> None:
"""Run templatisation on all files
"""
try:
ori_dir = getcwd()
except FileNotFoundError:
ori_dir = None
chdir(self.tmp_dir)
try:
if not self.rougail_variables_dict:
self.load_variables()
self.prepare_templates()
files_to_delete = []
for included in (True, False):
for service_obj in self.root.option('services').list('all'):
service_name = service_obj.description()
if service_obj.option('activate').value.get() is False:
if included is False and not service_obj.information.get('undisable', False):
self.desactive_service(service_name)
continue
if not included:
engine = service_obj.information.get('engine', None)
if engine:
self._instance_file({'engine': engine},
'service',
service_name,
extra_variables,
)
target_name = service_obj.information.get('target', None)
if target_name:
self.target_service(service_name,
target_name,
engine is None,
)
for fills in service_obj.list('optiondescription'):
type_ = fills.name()
for fill_obj in fills.list('all'):
fill = {path.split('.')[-1]: value for path, value in fill_obj.value.dict().items()}
self.get_default(type_, fill, fill_obj)
self.get_informations(type_, fill, fill_obj)
if 'included' in fill:
if (fill['included'] == 'no' and included is True) or \
(fill['included'] != 'no' and included is False):
continue
elif included is True:
continue
if fill['activate']:
destfilenames = self._instance_file(fill,
type_,
service_name,
extra_variables,
)
if included and fill.get('included', 'no') == 'content':
files_to_delete.extend(destfilenames)
elif 'name' in fill:
self.log.debug(_(f"Instantiation of file '{fill['name']}' disabled"))
self.post_instance_service(service_name)
for filename in files_to_delete:
unlink(filename)
parent = filename
while True:
parent = dirname(parent)
if listdir(parent):
break
rmdir(parent)
self.post_instance()
except Exception as err:
if ori_dir is not None:
chdir(ori_dir)
raise err
if ori_dir is not None:
chdir(ori_dir)
def prepare_templates(self):
for templates_dir in self.templates_dir:
for template in listdir(templates_dir):
self.prepare_template(template,
templates_dir,
)
def get_default(self,
type_: str,
dico: dict,
obj: 'Option',
) -> None:
for key in DEFAULT.get(type_, []):
default_key = f'default_{type_}_{key}'
if default_key in RougailConfig:
default_value = RougailConfig[default_key]
else:
default_value = undefined
dico[key] = dico.get(key, default_value)
def get_informations(self,
type_: str,
dico: dict,
obj: 'Option',
) -> None:
for key in INFORMATIONS.get(type_, []):
if key == 'target':
default_value = None
elif key == 'undisable':
default_value = False
elif key == 'engine' and type_ == 'service_names':
default_value = None
else:
default_key = f'default_{type_}_{key}'
if default_key in RougailConfig:
default_value = RougailConfig[default_key]
else:
default_value = undefined
value = obj.information.get(key, default_value)
if key != 'target' or value != default_value:
dico[key] = obj.information.get(key, default_value)
def desactive_service(self,
*args,
):
raise NotImplementedError(_('cannot desactivate a service'))
def target_service(self,
service_name: str,
*args,
):
raise NotImplementedError(_('cannot use target for the service {service_name}'))
def post_instance_service(self,
*args,
): # pragma: no cover
pass
def process(self,
filename: str,
destfilename: str,
mode: str,
owner: str,
group: str,
) -> None:
if owner not in [None, self.rougailconfig['default_files_owner']]:
#FIXME
raise TemplateError(_(f'cannot change owner of file {destfilename}'))
if group not in [None, self.rougailconfig['default_files_group']]:
#FIXME
raise TemplateError(_(f'cannot change group of file {destfilename}'))
if mode not in [None, self.rougailconfig['default_files_mode']]:
chmod(destfilename, eval(f'0o{mode}'))
def post_instance(self): # pragma: no cover
pass
def get_data_ip(self,
*args,
) -> None: # pragma: no cover
raise NotImplementedError(_('cannot instanciate this service type ip'))
def get_data_files(self,
filevar: Dict,
destfile: str,
service_name: str,
variable,
idx: int,
) -> None: # pragma: no cover
source = filevar['source']
if not isfile(source): # pragma: no cover
raise FileNotFound(_(f'Source file "{source}" does not exist in {", ".join(self.templates_dir)}'))
tmp_file = join(self.tmp_dir, source)
if variable:
var = variable[idx]
else:
var = None
return tmp_file, None, destfile, var
def get_data_service(self,
*args,
) -> None: # pragma: no cover
raise NotImplementedError(_('cannot instanciate this service'))
def get_data_overrides(self,
*args,
) -> None: # pragma: no cover
raise NotImplementedError(_('cannot instanciate this service type override'))
def get_data_certificates(self,
*args,
) -> None: # pragma: no cover
pass
def _load_variables(self,
optiondescription,
is_variable_namespace: str,
is_service_namespace: str,
) -> RougailExtra:
"""Load all variables and set it in RougailExtra objects
"""
variables = {}
if isinstance(self.root, TiramisuOption):
len_root_path = len(self.root.path()) + 1
for option in optiondescription.list('all'):
if option.isoptiondescription():
if option.isleadership():
for idx, suboption in enumerate(option.list('all')):
if idx == 0:
leader_name = suboption.name()
leader = RougailLeader(leader_name, suboption.value.get())
leadership_name = option.name()
if is_variable_namespace:
self.rougail_variables_dict[suboption.name()] = leader
else:
if isinstance(self.root, TiramisuOption):
path = (suboption.path())[len_root_path:]
else:
path = suboption.path()
leader._add_follower(self.root,
suboption.name(),
path,
)
variables[leadership_name] = RougailExtra(option.name(), {leader_name: leader}, option.path())
else:
if is_service_namespace == 'root':
new_is_service_namespace = 'service_name'
elif is_service_namespace == 'service_name':
new_is_service_namespace = option.name()
elif is_service_namespace in INFORMATIONS:
# remove 's'
new_is_service_namespace = is_service_namespace[:-1]
else:
new_is_service_namespace = is_service_namespace
subfamilies = self._load_variables(option,
is_variable_namespace,
new_is_service_namespace,
)
variables[option.name()] = subfamilies
else:
name = option.name()
value = option.value.get()
if is_variable_namespace:
self.rougail_variables_dict[name] = value
variables[name] = value
if isinstance(is_service_namespace, str) and is_service_namespace + 's' in INFORMATIONS:
self.get_default(is_service_namespace + 's',
variables,
optiondescription,
)
self.get_informations(is_service_namespace + 's',
variables,
optiondescription,
)
return RougailExtra(optiondescription.name(), variables, optiondescription.path())

View file

@ -1,4 +0,0 @@
from . import none, cheetah, jinja, creole_legacy
__all__ = ('none', 'cheetah', 'jinja', 'creole_legacy')

View file

@ -1,141 +0,0 @@
"""Creole engine
Created by:
EOLE (http://eole.orion.education.fr)
Copyright (C) 2005-2018
Forked by:
Cadoles (http://www.cadoles.com)
Copyright (C) 2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2023
distribued with GPL-2 or later license
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from os.path import abspath, normpath
from Cheetah.Template import Template
from Cheetah.NameMapper import NotFound
from typing import Dict, Any
from ...i18n import _
from ...utils import normalize_family
from ...error import TemplateError
@classmethod
def cl_compile(kls, *args, **kwargs):
"""Rewrite compile methode to force some settings
"""
kwargs['compilerSettings'] = {'directiveStartToken': '%',
'cheetahVarStartToken': '%%',
'commentStartToken': '#',
}
return kls.old_compile(*args, **kwargs) # pylint: disable=E1101
Template.old_compile = Template.compile
Template.compile = cl_compile
class CheetahTemplate(Template): # pylint: disable=W0223
"""Construct a cheetah templating object
"""
def __init__(self,
filename: str,
source: str,
context,
eosfunc: Dict,
extra_context: Dict,
):
"""Initialize Creole CheetahTemplate
"""
if filename is not None:
super().__init__(file=filename,
searchList=[context, eosfunc, extra_context],
)
else:
super().__init__(source=source,
searchList=[context, eosfunc, extra_context],
)
# FORK of Cheetah function, do not replace '\\' by '/'
def serverSidePath(self,
path=None,
normpath=normpath,
abspath=abspath
): # pylint: disable=W0621
# strange...
if path is None and isinstance(self, str):
path = self
if path: # pylint: disable=R1705
return normpath(abspath(path))
# original code return normpath(abspath(path.replace("\\", '/')))
elif hasattr(self, '_filePath') and self._filePath: # pragma: no cover
return normpath(abspath(self._filePath))
else: # pragma: no cover
return None
# Sync to creole_legacy.py
def process(filename: str,
source: str,
true_destfilename: str,
destfilename: str,
destdir: str,
variable: Any,
index: int,
rougail_variables_dict: Dict,
eosfunc: Dict,
extra_variables: Any=None,
):
"""Process a cheetah template
"""
# full path of the destination file
try:
extra_context = {'normalize_family': normalize_family,
'rougail_filename': true_destfilename,
'rougail_destination_dir': destdir,
}
if variable is not None:
extra_context['rougail_variable'] = variable
if index is not None:
extra_context['rougail_index'] = index
if extra_variables:
extra_context['extra_variables'] = extra_variables
cheetah_template = CheetahTemplate(filename,
source,
rougail_variables_dict,
eosfunc,
extra_context,
)
data = str(cheetah_template)
except NotFound as err: # pragma: no cover
varname = err.args[0][13:].split(' ', 1)[0][:-1]
if filename:
msg = f"Error: unknown variable used in template {filename} to {destfilename}: {varname}"
else:
msg = f"Error: unknown variable used in file {destfilename}: {varname}"
raise TemplateError(_(msg)) from err
except Exception as err: # pragma: no cover
if filename:
msg = _(f"Error while instantiating template {filename} to {destfilename}: {err}")
else:
msg = _(f"Error while instantiating filename {destfilename}: {err}")
raise TemplateError(msg) from err
with open(destfilename, 'w') as file_h:
file_h.write(data)

View file

@ -1,170 +0,0 @@
"""Legacy Creole engine
Created by:
EOLE (http://eole.orion.education.fr)
Copyright (C) 2005-2018
Forked by:
Cadoles (http://www.cadoles.com)
Copyright (C) 2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2023
distribued with GPL-2 or later license
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from typing import Dict, Any
from Cheetah.NameMapper import NotFound
from .cheetah import CheetahTemplate as oriCheetahTemplate
from ...i18n import _
from ...utils import normalize_family
from ...error import TemplateError
@classmethod
def cl_compile(kls, *args, **kwargs):
"""Rewrite compile methode to force some settings
"""
kwargs['compilerSettings'] = {'directiveStartToken' : u'%',
'cheetahVarStartToken' : u'%%',
'EOLSlurpToken' : u'%',
'PSPStartToken' : u'µ' * 10,
'PSPEndToken' : u'µ' * 10,
'commentStartToken' : u'µ' * 10,
'commentEndToken' : u'µ' * 10,
'multiLineCommentStartToken' : u'µ' * 10,
'multiLineCommentEndToken' : u'µ' * 10}
return kls.old_compile(*args, **kwargs) # pylint: disable=E1101
class IsDefined:
"""
filtre permettant de ne pas lever d'exception au cas où
la variable Creole n'est pas définie
"""
def __init__(self, context):
self.context = context
def __call__(self, varname):
if '.' in varname:
splitted_var = varname.split('.')
if len(splitted_var) != 2:
msg = u"Group variables must be of type master.slave"
raise KeyError(msg)
master, slave = splitted_var
if master in self.context:
return slave in self.context[master].slave.keys()
return False
else:
return varname in self.context
class CreoleClient():
def get(self, path):
path = path.replace('/', '.')
if path.startswith('.'):
path = path[1:]
if '.' not in path:
return self.context[path]
else:
root, path = path.split('.', 1)
obj = self.context[root]
for var in path.split('.'):
obj = getattr(obj, var)
return obj
def is_empty(data):
if str(data) in ['', '""', "''", "[]", "['']", '[""]', "None"]:
return True
return False
class CheetahTemplate(oriCheetahTemplate):
def __init__(self,
filename: str,
source: str,
context,
eosfunc: Dict,
extra_context: Dict,
):
creole_client = CreoleClient()
creole_client.context=context
extra_context['is_defined'] = IsDefined(context)
extra_context['creole_client'] = creole_client
extra_context['is_empty'] = is_empty
extra_context['_creole_filename'] = extra_context['rougail_filename']
super().__init__(filename, source, context, eosfunc, extra_context)
# Sync to creole.py
def process(filename: str,
source: str,
true_destfilename: str,
destfilename: str,
destdir: str,
variable: Any,
index: int,
rougail_variables_dict: Dict,
eosfunc: Dict,
extra_variables: Any=None,
):
"""Process a cheetah template
"""
# full path of the destination file
ori_compile = oriCheetahTemplate.compile
oriCheetahTemplate.compile = cl_compile
try:
extra_context = {'normalize_family': normalize_family,
'rougail_filename': true_destfilename,
'rougail_destination_dir': destdir,
}
if variable is not None:
extra_context['rougail_variable'] = variable
if index is not None:
extra_context['rougail_index'] = index
if extra_variables:
extra_context['extra_variables'] = extra_variables
cheetah_template = CheetahTemplate(filename,
source,
rougail_variables_dict,
eosfunc,
extra_context,
)
data = str(cheetah_template)
except NotFound as err: # pragma: no cover
varname = err.args[0][13:-1]
if filename:
msg = f"Error: unknown variable used in template {filename} to {destfilename}: {varname}"
else:
msg = f"Error: unknown variable used in file {destfilename}: {varname}"
oriCheetahTemplate.compile = ori_compile
raise TemplateError(_(msg)) from err
except Exception as err: # pragma: no cover
if filename:
msg = _(f"Error while instantiating template {filename} to {destfilename}: {err}")
else:
msg = _(f"Error while instantiating filename {destfilename}: {err}")
oriCheetahTemplate.compile = ori_compile
raise TemplateError(msg) from err
with open(destfilename, 'w') as file_h:
file_h.write(data)
oriCheetahTemplate.compile = ori_compile

View file

@ -1,81 +0,0 @@
"""Jinja engine
Created by:
EOLE (http://eole.orion.education.fr)
Copyright (C) 2005-2018
Forked by:
Cadoles (http://www.cadoles.com)
Copyright (C) 2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2023
distribued with GPL-2 or later license
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from typing import Any, Dict
from jinja2 import Environment, FileSystemLoader
from jinja2.exceptions import UndefinedError
from ...i18n import _
from ...utils import normalize_family
from ...error import TemplateError
def process(filename: str,
source: str,
true_destfilename: str,
destfilename: str,
destdir: str,
variable: Any,
index: int,
rougail_variables_dict: Dict,
eosfunc: Dict,
extra_variables: Any=None,
):
"""Process a cheetah template
"""
# full path of the destination file
dir_name, template_name = filename.rsplit('/', 1)
if source is not None: # pragma: no cover
raise TemplateError(_('source is not supported for jinja'))
var = {}
if variable is not None:
var['rougail_variable'] = variable
if index is not None:
var['rougail_index'] = index
if extra_variables:
var['extra_variables'] = extra_variables
try:
# extra_context = {'normalize_family': normalize_family,
# eosfunc
env = Environment(loader=FileSystemLoader([dir_name, destdir]))
template = env.get_template(template_name)
data = template.render(**rougail_variables_dict,
rougail_filename=true_destfilename,
rougail_destination_dir=destdir,
**var,
)
except UndefinedError as err: # pragma: no cover
varname = err
msg = f"Error: unknown variable used in template {filename} to {destfilename}: {varname}"
raise TemplateError(_(msg)) from err
if not data.endswith('\n'):
data = data + '\n'
with open(destfilename, 'w') as file_h:
file_h.write(data)

View file

@ -1,43 +0,0 @@
"""None engine
Created by:
EOLE (http://eole.orion.education.fr)
Copyright (C) 2005-2018
Forked by:
Cadoles (http://www.cadoles.com)
Copyright (C) 2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2023
distribued with GPL-2 or later license
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from typing import Any
from shutil import copy
def process(filename: str,
source: str,
destfilename: str,
**kwargs
):
if filename is not None:
copy(filename, destfilename)
else:
with open(destfilename, 'w') as fh:
fh.write(source)

View file

@ -1,206 +0,0 @@
"""Template langage for Rougail to create file and systemd file
Cadoles (http://www.cadoles.com)
Copyright (C) 2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2023
distribued with GPL-2 or later license
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from typing import Dict, Any
from os import makedirs, symlink
from os.path import dirname, isfile, join
from ipaddress import ip_network
from .base import RougailBaseTemplate
from ..i18n import _
from ..error import FileNotFound, TemplateError
ROUGAIL_IP_TEMPLATE = """[Service]
%for %%ip in %%rougail_variable
IPAddressAllow=%%ip
%end for
IPAddressDeny=any
"""
LOCAL_DIR = ('/etc/', '/var/', '/srv/')
class RougailSystemdTemplate(RougailBaseTemplate):
def __init__(self, # pylint: disable=R0913
config: 'Config',
rougailconfig: 'RougailConfig'=None,
) -> None:
self.ip_per_service = None
super().__init__(config, rougailconfig)
self.rougail_tmpl_template = f"""%def display(%%file, %%filename, %%service_activate, %%file_activate)
"""
tmp_local_dir = (f"%%filename.startswith('{local_dir}')" for local_dir in LOCAL_DIR)
self.rougail_tmpl_template += '%if ' + ' or '.join(tmp_local_dir)
self.rougail_tmpl_template += f"""
%if {self.rougailconfig['systemd_tmpfile_delete_before_create']}
r %%filename
%end if
%if %%service_activate and %%file_activate
%set %%mode = %%str(%%file.mode)
%if %%len(%%mode) == 3
%set %%mode = '0' + %%mode
%end if
C %%filename %%mode %%file.owner %%file.group - {self.rougailconfig['systemd_tmpfile_factory_dir']}%%filename
%end if
%end if
%end def
%for %%service in %%services
%if %%hasattr(%%service, 'files')
%for %%file in %%service.files
%if %%hasattr(%%file, 'name') and %%file.included != 'content'
%if %%isinstance(%%file.name, list)
%for %%filename in %%file.name
%%display(%%file, %%filename, %%service.activate, %%file.activate)%slurp
%end for
%else
%%display(%%file, %%file.name, %%service.activate, %%file.activate)%slurp
%end if
%end if
%end for
%end if
%end for
"""
def get_data_overrides(self,
filevar: Dict,
destfile,
service_name: str,
*args,
) -> tuple:
source = filevar['source']
if not isfile(source): # pragma: no cover
raise FileNotFound(_(f'Override source file "{source}" does not exist in {", ".join(self.templates_dir)}'))
tmp_file = join(self.tmp_dir, source)
service_name = filevar['name']
destfile = f'{self.rougailconfig["systemd_service_directory"]}/system/{service_name}.d/{self.rougailconfig["systemd_service_file"]}'
return tmp_file, None, destfile, None
def get_data_ip(self,
filevar: Dict,
ip,
service_name: str,
var: Any,
idx: int,
*args,
) -> tuple:
if self.ip_per_service is None:
self.ip_per_service = []
if 'netmask' in filevar:
if isinstance(filevar["netmask"], list):
netmask = filevar['netmask'][idx]
else:
netmask = filevar['netmask']
self.ip_per_service.append(str(ip_network(f'{ip}/{netmask}')))
elif ip:
self.ip_per_service.append(ip)
def get_data_service(self,
servicevar: Dict,
info,
service_name: str,
*args,
):
tmp_file = join(self.tmp_dir, service_name)
var = None
destfile = f'{self.rougailconfig["systemd_service_directory"]}/system/{service_name}'
return tmp_file, None, destfile, var
def desactive_service(self,
service_name: str,
):
filename = f'{self.destinations_dir}/{self.rougailconfig["systemd_service_directory"]}/system/{service_name}'
makedirs(dirname(filename), exist_ok=True)
symlink('/dev/null', filename)
def target_service(self,
service_name: str,
target_name: str,
system_service: bool,
):
"""system_service: means that the service is not generated by rougail
"""
filename = f'{self.destinations_dir}/{self.rougailconfig["systemd_service_directory"]}/system/{target_name}.target.wants/{service_name}'
makedirs(dirname(filename), exist_ok=True)
if system_service:
source_filename = f'{self.rougailconfig["system_service_directory"]}/{service_name}'
else:
source_filename = f'{self.rougailconfig["systemd_service_destination_directory"]}{self.rougailconfig["systemd_service_directory"]}/system/{service_name}'
symlink(source_filename, filename)
def post_instance_service(self,
service_name: str,
) -> None: # pragma: no cover
if self.ip_per_service is None:
return
destfile = f'{self.rougailconfig["systemd_service_directory"]}/system/{service_name}.d/{self.rougailconfig["systemd_service_ip_file"]}'
destfilename = join(self.destinations_dir, destfile[1:])
makedirs(dirname(destfilename), exist_ok=True)
self.log.info(_(f"Cheetah processing: '{destfilename}'"))
self.engines['cheetah'].process(filename=None,
source=ROUGAIL_IP_TEMPLATE,
true_destfilename=destfile,
destfilename=destfilename,
destdir=self.destinations_dir,
variable=self.ip_per_service,
index=None,
rougail_variables_dict=self.rougail_variables_dict,
eosfunc=self.eosfunc,
)
self.ip_per_service = None
def process(self,
filename: str,
destfilename: str,
mode: str,
owner: str,
group: str,
) -> None:
for local_dir in LOCAL_DIR:
if filename.startswith(local_dir):
return
if owner not in [None, self.rougailconfig['default_files_owner']]:
raise TemplateError(_(f'cannot change owner of file {destfilename}'))
if group not in [None, self.rougailconfig['default_files_group']]:
raise TemplateError(_(f'cannot change group of file {destfilename}'))
super().process(filename, destfilename, mode, owner, group)
def post_instance(self):
tmpfiles_filename = f"{self.rougailconfig['systemd_tmpfile_directory']}/{self.rougailconfig['systemd_tmpfile_file']}"
destfilename = join(self.destinations_dir, tmpfiles_filename[1:])
makedirs(dirname(destfilename), exist_ok=True)
self.log.info(_(f"Cheetah processing: '{destfilename}'"))
self.engines['cheetah'].process(filename=None,
source=self.rougail_tmpl_template,
true_destfilename=tmpfiles_filename,
destfilename=destfilename,
destdir=self.destinations_dir,
variable=None,
index=None,
rougail_variables_dict=self.rougail_variables_dict,
eosfunc=self.eosfunc,
)

View file

@ -28,6 +28,7 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from typing import Optional
from json import dumps
from os.path import isfile, basename
@ -36,12 +37,14 @@ from .annotator import CONVERT_OPTION
from .objspace import RootRougailObject
from .error import DictConsistencyError
from .utils import normalize_family
from .object_model import Calculation
class BaseElt: # pylint: disable=R0903
"""Base element
"""
path = '.'
type = 'family'
def sorted_func_name(func_name):
@ -56,21 +59,28 @@ class TiramisuReflector:
def __init__(self,
objectspace,
funcs_paths,
internal_functions,
cfg,
):
self.cfg = cfg
self.rougailconfig = objectspace.rougailconfig
self.jinja_added = False
self.reflector_objects = {}
self.text = {'header': [],
'option': [],
'optiondescription': [],
}
if self.rougailconfig['export_with_import']:
if self.rougailconfig['internal_functions']:
for func in self.rougailconfig['internal_functions']:
self.text['header'].append(f"func[func] = func")
self.text['header'].extend(["from tiramisu import *",
"from tiramisu.setting import ALLOWED_LEADER_PROPERTIES",
])
for mode in self.rougailconfig["modes_level"]:
self.text['header'].append(f'ALLOWED_LEADER_PROPERTIES.add("{mode}")')
if funcs_paths:
if self.cfg['export_with_import']:
if self.rougailconfig['export_with_import']:
self.text['header'].extend(["from importlib.machinery import SourceFileLoader as _SourceFileLoader",
"from importlib.util import spec_from_loader as _spec_from_loader, module_from_spec as _module_from_spec",
"class func:",
" pass",
"global func",
"func = {'calc_value': calc_value}",
"",
"def _load_functions(path):",
" global _SourceFileLoader, _spec_from_loader, _module_from_spec, func",
@ -81,39 +91,27 @@ class TiramisuReflector:
" for function in dir(func_):",
" if function.startswith('_'):",
" continue",
" setattr(func, function, getattr(func_, function))",
" func[function] = getattr(func_, function)",
])
for funcs_path in sorted(funcs_paths, key=sorted_func_name):
if not isfile(funcs_path):
continue
self.text['header'].append(f"_load_functions('{funcs_path}')")
if self.cfg['export_with_import']:
if internal_functions:
for func in internal_functions:
self.text['header'].append(f"setattr(func, '{func}', {func})")
self.text['header'].extend(["try:",
" from tiramisu4 import *",
" from tiramisu4.setting import ALLOWED_LEADER_PROPERTIES",
"except:",
" from tiramisu import *",
" from tiramisu.setting import ALLOWED_LEADER_PROPERTIES",
])
for mode in objectspace.rougailconfig["modes_level"]:
self.text['header'].append(f'ALLOWED_LEADER_PROPERTIES.add("{mode}")')
self.objectspace = objectspace
self.make_tiramisu_objects()
if self.cfg['export_with_import'] and (self.cfg['force_convert_dyn_option_description'] or self.objectspace.has_dyn_option is True):
if self.rougailconfig['export_with_import'] and (self.rougailconfig['force_convert_dyn_option_description'] or self.objectspace.has_dyn_option is True):
self.text['header'].append("from rougail.tiramisu import ConvertDynOptionDescription")
for key, value in self.objectspace.jinja.items():
self.add_jinja_to_function(key, value)
def add_jinja_to_function(self,
variable_name: str,
jinja: str,
) -> None:
def add_jinja_support(self):
if not self.jinja_added:
self.text['header'].extend(["from jinja2 import StrictUndefined, DictLoader",
"from jinja2.sandbox import SandboxedEnvironment",
"from rougail.annotator.variable import CONVERT_OPTION",
"from tiramisu.error import ValueWarning",
"def jinja_to_function(__internal_jinja, __internal_type, __internal_multi, **kwargs):",
" global ENV, CONVERT_OPTION",
" kw = {}",
" for key, value in kwargs.items():",
" if '.' in key:",
@ -124,21 +122,42 @@ class TiramisuReflector:
" c_kw[var] = value",
" else:",
" kw[key] = value",
" values = ENV.get_template(__internal_jinja).render(kw)",
" values = ENV.get_template(__internal_jinja).render(kw, **func).strip()",
" convert = CONVERT_OPTION[__internal_type].get('func', str)",
" if __internal_multi:",
" return [convert(val) for val in values.split(',')]",
" return convert(values)",
"def valid_with_jinja(value, **kwargs):",
" kwargs[kwargs.pop('__internal_key')] = value",
" value = jinja_to_function(__internal_type='string', __internal_multi=False, **kwargs)",
" return [convert(val) for val in values.split()]",
" values = convert(values)",
" return values if values != '' and values != 'None' else None",
"def variable_to_property(prop, value):",
" return prop if value else None",
"def jinja_to_property(prop, **kwargs):",
" value = func['jinja_to_function'](**kwargs)",
" return func['variable_to_property'](prop, value is not None)",
"def jinja_to_property_help(prop, **kwargs):",
" value = func['jinja_to_function'](**kwargs)",
" return (prop, f'\"{prop}\" ({value})')",
"def valid_with_jinja(warnings_only=False, **kwargs):",
" global ValueWarning",
" value = func['jinja_to_function'](**kwargs)",
" if value:",
" raise ValueError(value)",
"func.jinja_to_function = jinja_to_function",
"func.valid_with_jinja = valid_with_jinja",
" if warnings_only:",
" raise ValueWarning(value)",
" else:",
" raise ValueError(value)",
"func['jinja_to_function'] = jinja_to_function",
"func['jinja_to_property'] = jinja_to_property",
"func['jinja_to_property_help'] = jinja_to_property_help",
"func['variable_to_property'] = variable_to_property",
"func['valid_with_jinja'] = valid_with_jinja",
"dict_env = {}",
])
self.jinja_added = True
def add_jinja_to_function(self,
variable_name: str,
jinja: str,
) -> None:
self.add_jinja_support()
jinja_text = dumps(jinja, ensure_ascii=False)
self.text['header'].append(f"dict_env['{variable_name}'] = {jinja_text}")
@ -146,20 +165,30 @@ class TiramisuReflector:
"""make tiramisu objects
"""
baseelt = BaseElt()
baseelt.reflector_name = f'option_0{self.objectspace.rougailconfig["suffix"]}'
self.set_name(baseelt)
self.objectspace.reflector_names[baseelt.path] = f'option_0{self.rougailconfig["suffix"]}'
basefamily = Family(baseelt,
self,
)
if not self.objectspace.paths.has_path_prefix():
for elt in self.reorder_family(self.objectspace.space):
self.populate_family(basefamily,
elt,
)
if not hasattr(basefamily.elt, 'information'):
basefamily.elt.information = self.objectspace.information(None)
basefamily.elt.information = self.objectspace.paths.get_providers_path()
basefamily.elt.information.update(self.objectspace.paths.get_suppliers_path())
#FIXMEif not self.objectspace.paths.has_path_prefix():
if 1:
# for elt in self.reorder_family(self.objectspace.space):
for elt in self.objectspace.paths.get():
if elt.path in self.objectspace.families:
Family(elt,
self,
)
else:
Variable(elt,
self,
)
# self.populate_family(basefamily,
# elt,
# )
#FIXME if not hasattr(basefamily.elt, 'information'):
# basefamily.elt.information = self.objectspace.information(None)
# basefamily.elt.information = self.objectspace.paths.get_providers_path()
# basefamily.elt.information.update(self.objectspace.paths.get_suppliers_path())
else:
path_prefixes = self.objectspace.paths.get_path_prefixes()
for path_prefix in path_prefixes:
@ -179,75 +208,76 @@ class TiramisuReflector:
setattr(baseprefix.elt.information, key, value)
for key, value in self.objectspace.paths.get_suppliers_path(path_prefix).items():
setattr(baseprefix.elt.information, key, value)
baseelt.name = normalize_family(self.cfg['base_option_name'])
baseelt.doc = self.cfg['base_option_name']
baseelt.reflector_object.get([], baseelt.doc, 'base') # pylint: disable=E1101
def reorder_family(self, space):
"""variable_namespace family has to be loaded before any other family
because `extra` family could use `variable_namespace` variables.
"""
if hasattr(space, 'variables'):
variable_namespace = self.objectspace.rougailconfig['variable_namespace']
if variable_namespace in space.variables:
yield space.variables[variable_namespace]
for elt, value in space.variables.items():
if elt != self.objectspace.rougailconfig['variable_namespace']:
yield value
if hasattr(space, 'services'):
yield space.services
def populate_family(self,
parent_family,
elt,
):
"""Populate family
"""
self.set_name(elt)
family = Family(elt,
self,
)
parent_family.add(family)
for children in vars(elt).values():
if isinstance(children, self.objectspace.family):
self.populate_family(family,
children,
)
continue
if isinstance(children, dict):
children = list(children.values())
if isinstance(children, list):
for child in children:
if isinstance(child, self.objectspace.property_) or \
not isinstance(child, RootRougailObject):
continue
if isinstance(child, self.objectspace.variable):
self.set_name(child)
family.add(Variable(child,
self,
))
else:
self.populate_family(family,
child,
)
baseelt.name = normalize_family(self.rougailconfig['base_option_name'])
baseelt.description = self.rougailconfig['base_option_name']
self.reflector_objects[baseelt.path].get([], baseelt.description) # pylint: disable=E1101
#
# def reorder_family(self, space):
# """family has to be loaded before any other family
# because `extra` family could use `variable_namespace` variables.
# """
# if hasattr(space, 'variables'):
# variable_namespace = self.rougailconfig['variable_namespace']
# if variable_namespace in space.variables:
# yield space.variables[variable_namespace]
# for elt, value in space.variables.items():
# if elt != self.rougailconfig['variable_namespace']:
# yield value
# if hasattr(space, 'services'):
# yield space.services
#
# def populate_family(self,
# parent_family,
# elt,
# ):
# """Populate family
# """
# self.set_name(elt)
# family = Family(elt,
# self,
# )
# parent_family.add(family)
# for children in vars(elt).values():
# if isinstance(children, self.objectspace.family):
# self.populate_family(family,
# children,
# )
# continue
# if isinstance(children, dict):
# children = list(children.values())
# if isinstance(children, list):
# for child in children:
# if isinstance(child, self.objectspace.property_) or \
# not isinstance(child, RootRougailObject):
# continue
# if isinstance(child, self.objectspace.variable):
# self.set_name(child)
# family.add(Variable(child,
# self,
# ))
# else:
# self.populate_family(family,
# child,
# )
def set_name(self,
elt,
):
"""Set name
"""
if not hasattr(elt, 'reflector_name'):
self.objectspace.paths.set_name(elt, 'optiondescription_')
return elt.reflector_name
if elt.path not in self.objectspace.reflector_names:
self.objectspace.set_name(elt, 'optiondescription_')
return self.objectspace.reflector_names[elt.path]
def get_text(self):
"""Get text
"""
if self.jinja_added:
self.text['header'].extend(["ENV = SandboxedEnvironment(loader=DictLoader(dict_env), undefined=StrictUndefined)",
"ENV.filters = func",
"ENV.compile_templates('jinja_caches', zip=None)",
])
return '\n'.join(self.text['header'] + self.text['option'] + self.text['optiondescription'])
return '\n'.join(self.text['header'] + self.text['option'])
class Common:
@ -257,13 +287,14 @@ class Common:
elt,
tiramisu,
):
self.objectspace = tiramisu.objectspace
self.elt = elt
self.option_name = None
self.tiramisu = tiramisu
self.elt.reflector_object = self
tiramisu.reflector_objects[elt.path] = self
self.object_type = None
def get(self, calls, parent_name, typ):
def get(self, calls, parent_name):
"""Get tiramisu's object
"""
self_calls = calls.copy()
@ -273,7 +304,7 @@ class Common:
self_calls.append(self.elt.path)
self.calls = self_calls
if self.option_name is None:
self.option_name = self.elt.reflector_name
self.option_name = self.objectspace.reflector_names[self.elt.path]
self.populate_attrib()
self.populate_informations()
return self.option_name
@ -282,19 +313,13 @@ class Common:
"""Populate attributes
"""
keys = {'name': self.convert_str(self.elt.name)}
if hasattr(self.elt, 'doc'):
keys['doc'] = self.convert_str(self.elt.doc)
if hasattr(self.elt, 'description') and self.elt.description:
keys['doc'] = self.convert_str(self.elt.description)
self._populate_attrib(keys)
if hasattr(self.elt, 'properties'):
keys['properties'] = self.properties_to_string(self.elt.properties)
if self.elt.path in self.objectspace.properties:
keys['properties'] = self.properties_to_string(self.objectspace.properties[self.elt.path])
attrib = ', '.join([f'{key}={value}' for key, value in keys.items()])
if self.__class__.__name__ == 'Family':
#pouet
name = 'option'
#name = 'optiondescription'
else:
name = 'option'
self.tiramisu.text[name].append(f'{self.option_name} = {self.object_type}({attrib})')
self.tiramisu.text['option'].append(f'{self.option_name} = {self.object_type}({attrib})')
def _populate_attrib(self,
keys: dict,
@ -305,6 +330,8 @@ class Common:
def convert_str(value):
"""convert string
"""
if value is None:
return 'None'
return dumps(value, ensure_ascii=False)
def properties_to_string(self,
@ -312,37 +339,40 @@ class Common:
) -> None:
"""Change properties to string
"""
properties = [self.convert_str(property_) for property_ in values
if isinstance(property_, str)]
calc_properties = [self.calc_properties(property_) for property_ in values \
if isinstance(property_, self.tiramisu.objectspace.property_)]
properties = []
calc_properties = []
for property_, value in values.items():
if value is True:
properties.append(self.convert_str(property_))
else:
if isinstance(value, list):
for val in value:
calc_properties.append(self.calculation_value(val))
else:
calc_properties.append(self.calculation_value(value))
return 'frozenset({' + ', '.join(sorted(properties) + calc_properties) + '})'
def calc_properties(self,
child,
prop,
calculation,
) -> str:
"""Populate properties
"""
option_name = child.source.reflector_object.get(self.calls, self.elt.path, 'property')
option_name = self.tiramisu.reflector_objects[child.source.path].get(self.calls, self.elt.path)
kwargs = (f"'condition': ParamOption({option_name}, notraisepropertyerror=True), "
f"'expected': {self.populate_param(child.expected)}")
if child.inverse:
kwargs += ", 'reverse_condition': ParamValue(True)"
return (f"Calculation(func.calc_value, Params(ParamValue('{child.name}'), "
f"kwargs={{{kwargs}}}), func.calc_value_property_help)")
return (f"Calculation(func['calc_value'], Params(ParamValue('{child.name}'), "
f"kwargs={{{kwargs}}}), func['calc_value_property_help'])")
def populate_informations(self):
"""Populate Tiramisu's informations
"""
if not hasattr(self.elt, 'information'):
informations = self.objectspace.informations.get(self.elt.path)
if not informations:
return
if isinstance(self.elt.information, dict):
informations = self.elt.information
else:
informations = vars(self.elt.information)
for key, value in informations.items():
if key == 'xmlfiles':
continue
if isinstance(value, str):
value = self.convert_str(value)
self.tiramisu.text['option'].append(f"{self.option_name}.impl_set_information('{key}', {value})")
@ -352,51 +382,87 @@ class Common:
):
"""Populate variable parameters
"""
if param.type in ['number', 'boolean', 'nil', 'port', 'choice', 'space']:
return f'ParamValue({param.text})'
if param.type in ['variable_name', 'variable']:
return self.build_option_param(param)
if param.type == 'information':
if hasattr(self.elt, 'multi') and self.elt.multi:
if not isinstance(param, dict):
if isinstance(param, str):
value = self.convert_str(param)
else:
value = param
return f'ParamValue({value})'
if param['type'] == 'information':
if self.elt.multi:
default = []
else:
default = None
if hasattr(param, 'variable'):
if param.variable.path == self.elt.path:
return f'ParamSelfInformation("{param.text}", {default})'
return f'ParamInformation("{param.text}", {default}, option={param.variable.reflector_object.get(self.calls, self.elt.path, "param")})'
return f'ParamInformation("{param.text}", {default})'
if param.type == 'suffix':
if 'variable' in param:
if param['variable'].path == self.elt.path:
return f'ParamSelfInformation("{param["information"]}", {default})'
return f'ParamInformation("{param["information"]}", {default}, option={self.tiramisu.reflector_objects[param["variable"].path].get(self.calls, self.elt.path)})'
return f'ParamInformation("{param["information"]}", {default})'
if param['type'] == 'suffix':
return 'ParamSuffix()'
if param.type == 'index':
if param['type'] == 'index':
return 'ParamIndex()'
if param.type == 'jinja':
self.tiramisu.add_jinja_to_function(self.elt.path, param.text)
return f'ParamValue("{self.elt.path}")'
value = self.convert_str(param.text)
return f'ParamValue({value})'
if param['type'] == 'variable':
return self.build_option_param(param['variable'],
param.get('propertyerror', True),
param.get('suffix'),
)
if param['type'] == 'any':
if isinstance(param['value'], str):
value = self.convert_str(param['value'])
else:
value = str(param['value'])
return 'ParamValue(' + value + ')'
raise Exception('pfff')
def build_option_param(self,
param,
propertyerror,
suffix: Optional[str],
) -> str:
"""build variable parameters
"""
if param.type == 'variable':
option_name = param.text.reflector_object.get(self.calls, self.elt.path, 'param')
else:
option_name = param.text
if param.path == self.elt.path:
return 'ParamSelfOption(whole=False)'
option_name = self.tiramisu.reflector_objects[param.path].get(self.calls, self.elt.path)
params = [f'{option_name}']
if hasattr(param, 'suffix'):
if suffix is not None:
param_type = 'ParamDynOption'
family = param.family.reflector_object.get(self.calls, self.elt.path, 'suffix')
params.extend([f"'{param.suffix}'", f'{family}'])
if param.optional:
params.append('optional=True')
family = self.tiramisu.reflector_objects[param.path.rsplit('.', 1)[0]].get(self.calls, self.elt.path)
params.extend([f"'{suffix}'", f'{family}'])
else:
param_type = 'ParamOption'
if not param.propertyerror:
if not propertyerror:
params.append('notraisepropertyerror=True')
return "{}({})".format(param_type, ', '.join(params))
return f'{param_type}({", ".join(params)})'
def calculation_value(self,
function,
) -> str:
"""Generate calculated value
"""
self.tiramisu.add_jinja_support()
child = function.to_function(self.objectspace)
new_args = []
kwargs = []
if 'params' in child:
for key, value in child['params'].items():
if not key:
for val in value:
new_args.append(self.populate_param(val))
else:
kwargs.append(f"'{key}': " + self.populate_param(value))
ret = f"Calculation(func['{child['function']}'], Params((" + ', '.join(new_args) + ')'
if kwargs:
ret += ', kwargs={' + ', '.join(kwargs) + '}'
ret += ')'
if hasattr(child, 'warnings_only'):
print('HU????')
ret += f', warnings_only={child.warnings_only}'
if 'help' in child:
ret += f", help_function=func['{child['help']}']"
ret = ret + ')'
return ret
class Variable(Common):
@ -412,71 +478,72 @@ class Variable(Common):
def _populate_attrib(self,
keys: dict,
):
if hasattr(self.elt, 'opt'):
keys['opt'] = self.elt.opt.reflector_object.get(self.calls, self.elt.path, 'opt')
if hasattr(self.elt, 'choice'):
values = self.elt.choice
if values[0].type == 'variable':
value = values[0].name.reflector_object.get(self.calls, self.elt.path, 'choice')
keys['values'] = f"Calculation(func.calc_value, Params((ParamOption({value}))))"
elif values[0].type == 'function':
keys['values'] = self.calculation_value(values[0], [])
if self.elt.type == 'symlink':
keys['opt'] = self.tiramisu.reflector_objects[self.elt.opt.path].get(self.calls, self.elt.path)
if self.elt.type == 'choice':
choices = self.elt.choices
if isinstance(choices, Calculation):
keys['values'] = self.calculation_value(choices)
# if values['type'] == 'variable':
# value = self.tiramisu.reflector_objects[values['variable'].path].get(self.calls, self.elt.path)
# keys['values'] = f"Calculation(func['calc_value'], Params((ParamOption({value}))))"
# elif values['type'] == 'jinja':
# keys['values'] = self.calculation_value(values)
else:
keys['values'] = str(tuple([val.name for val in values]))
if hasattr(self.elt, 'multi') and self.elt.multi:
keys['multi'] = self.elt.multi
for key in ['default', 'default_multi']:
if hasattr(self.elt, key) and getattr(self.elt, key) is not None:
value = getattr(self.elt, key)
if isinstance(value, str):
value = self.convert_str(value)
elif isinstance(value, self.tiramisu.objectspace.value):
value = self.calculation_value(value, [], calc_multi=value.calc_multi)
keys[key] = value
if hasattr(self.elt, 'validators'):
keys['validators'] = '[' + ', '.join([self.calculation_value(val,
['ParamSelfOption(whole=False)']) for val in self.elt.validators]) + ']'
for key in ['min_number', 'max_number']:
if hasattr(self.elt, key):
keys[key] = getattr(self.elt, key)
new_values = []
for value in choices:
if isinstance(value, Calculation):
new_values.append(self.calculation_value(value))
elif isinstance(value, str):
new_values.append(self.convert_str(value))
else:
new_values.append(str(value))
keys['values'] = '(' + ', '.join(new_values)
if len(new_values) <= 1:
keys['values'] += ','
keys['values'] += ')'
if self.elt.path in self.objectspace.multis:
keys['multi'] = self.objectspace.multis[self.elt.path]
if hasattr(self.elt, 'default') and self.elt.default is not None:
value = self.elt.default
if isinstance(value, str):
value = self.convert_str(value)
elif isinstance(value, Calculation):
value = self.calculation_value(value)
elif isinstance(value, list):
for idx, val in enumerate(value):
if isinstance(val, Calculation):
value[idx] = self.calculation_value(val)
else:
value[idx] = self.convert_str(val)
value = '[' + ', '.join(value) + ']'
keys['default'] = value
if self.elt.path in self.objectspace.default_multi:
value = self.objectspace.default_multi[self.elt.path]
if isinstance(value, str):
value = self.convert_str(value)
elif isinstance(value, Calculation):
value = self.calculation_value(value)
keys['default_multi'] = value
if self.elt.validators:
validators = []
for val in self.elt.validators:
if isinstance(val, Calculation):
validators.append(self.calculation_value(val))
else:
validators.append(val)
keys['validators'] = '[' + ', '.join(validators) + ']'
#keys['validators'] = '[' + ', '.join([self.calculation_value(val)['ParamSelfOption(whole=False)']) for val in self.elt.validators]) + ']'
for key, value in CONVERT_OPTION[self.elt.type].get('initkwargs', {}).items():
if isinstance(value, str):
value = f"'{value}'"
keys[key] = value
def calculation_value(self,
child,
args,
calc_multi=False,
) -> str:
"""Generate calculated value
"""
kwargs = []
# has parameters
function = child.name
new_args = []
if hasattr(child, 'param'):
for param in child.param:
value = self.populate_param(param)
if not hasattr(param, 'name'):
new_args.append(str(value))
else:
kwargs.append(f"'{param.name}': " + value)
if function == 'valid_network_netmask':
new_args.extend(args)
else:
args.extend(new_args)
new_args = args
ret = f'Calculation(func.{function}, Params((' + ', '.join(new_args) + ')'
if kwargs:
ret += ', kwargs={' + ', '.join(kwargs) + '}'
ret += ')'
if hasattr(child, 'warnings_only'):
ret += f', warnings_only={child.warnings_only}'
ret = ret + ')'
if calc_multi:
ret = '[' + ret + ']'
return ret
if self.elt.params:
for param in self.elt.params:
value = param.value
if isinstance(value, str):
value = self.convert_str(value)
keys[param.key] = value
class Family(Common):
@ -487,10 +554,10 @@ class Family(Common):
tiramisu,
):
super().__init__(elt, tiramisu)
if hasattr(self.elt, 'suffixes'):
if self.elt.type == 'dynamic':
self.tiramisu.objectspace.has_dyn_option = True
self.object_type = 'ConvertDynOptionDescription'
elif hasattr(self.elt, 'leadership') and self.elt.leadership:
elif self.elt.type == 'leadership':
self.object_type = 'Leadership'
else:
self.object_type = 'OptionDescription'
@ -504,7 +571,10 @@ class Family(Common):
def _populate_attrib(self,
keys: list,
) -> None:
if hasattr(self.elt, 'suffixes'):
dyn = self.elt.suffixes.reflector_object.get(self.calls, self.elt.path, 'suffixes')
keys['suffixes'] = f"Calculation(func.calc_value, Params((ParamOption({dyn}, notraisepropertyerror=True))))"
keys['children'] = '[' + ', '.join([child.get(self.calls, self.elt.path, 'child') for child in self.children]) + ']'
if self.elt.type == 'dynamic':
dyn = self.tiramisu.reflector_objects[self.elt.variable.path].get(self.calls, self.elt.path)
keys['suffixes'] = f"Calculation(func['calc_value'], Params((ParamOption({dyn}, notraisepropertyerror=True))))"
children = []
for path in self.objectspace.parents[self.elt.path]:
children.append(self.objectspace.paths[path])
keys['children'] = '[' + ', '.join([self.tiramisu.reflector_objects[child.path].get(self.calls, self.elt.path) for child in children]) + ']'

File diff suppressed because it is too large Load diff

View file

@ -27,13 +27,18 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
from typing import List
from typing import List, Union
from unicodedata import normalize, combining
import re
from importlib.machinery import SourceFileLoader
from importlib.util import spec_from_loader, module_from_spec
from jinja2 import DictLoader, TemplateSyntaxError
from jinja2.sandbox import SandboxedEnvironment
from jinja2.parser import Parser
from jinja2.nodes import Getattr
from .i18n import _
from .error import DictConsistencyError
@ -69,3 +74,246 @@ def load_modules(eosfunc_file) -> List[str]:
eosfunc = module_from_spec(spec)
loader.exec_module(eosfunc)
return eosfunc
def get_realpath(path: str,
path_prefix: str,
) -> str:
if path_prefix:
return f'{path_prefix}.{path}'
return path
def get_jinja_variable_to_param(jinja_text,
objectspace,
xmlfiles,
functions,
path_prefix,
):
try:
env = SandboxedEnvironment(loader=DictLoader({'tmpl': jinja_text}))
env.filters = functions
parsed_content = Parser(env, jinja_text, "", "").parse()
def recurse_getattr(g: Getattr):
if isinstance(g.node, Getattr):
return recurse_getattr(g.node) + "." + g.attr
return g.node.name + "." + g.attr
variables = set()
for g in parsed_content.find_all(Getattr):
variables.add(recurse_getattr(g))
except TemplateSyntaxError as err:
msg = _(f'error in jinja "{jinja_text}": {err}')
raise Exception(msg) from err
variables = list(variables)
variables.sort()
for variable_path in variables:
variable, suffix = objectspace.paths.get_with_dynamic(get_realpath(variable_path, path_prefix))
if variable and variable.path in objectspace.variables:
yield variable, suffix, variable_path
class CalculationOld:
def __init__(self,
object_name: str,
attribute: str,
path: str,
datas: Union[dict, List[dict]],
filenames: List[str],
objectspace: 'RougailConvert',
path_prefix: str,
inside_list: bool,
) -> None:
self.path_prefix = path_prefix
if 'type' not in datas:
raise Exception(f'the {object_name} "{path}" in "{filenames}" must have a "type" attribut for "{attribute}"')
self.type = datas.pop('type')
self.path = path
self.attribute = attribute
if self.type != 'suffix':
self.value = datas.pop(self.type)
if 'params' in datas:
if self.type == 'variable':
raise Exception('param not allowed with type variable')
self.params = datas.pop('params')
else:
self.params = {}
if 'return_type' in datas:
self.return_type = datas.pop('return_type')
if self.return_type not in objectspace.variable_types:
raise Exception(f'unknown "return_type" in {attribute} of variable "{self.path}"')
else:
self.return_type = None
if self.type == 'variable':
self.propertyerror = datas.pop('propertyerror', True)
if datas:
raise Exception('hu.?')
self.objectspace = objectspace
self.inside_list = inside_list
def get_realpath(self,
path: str,
) -> str:
return get_realpath(path, self.path_prefix)
def change_variable_in_params(self):
if self.params:
delete_keys = []
for key, value in self.params.items():
if isinstance(value, dict):
if value['type'] == 'variable':
variable_path = self.get_realpath(value['variable'])
variable, suffix = self.objectspace.paths.get_with_dynamic(variable_path)
if variable:
if not isinstance(variable, self.objectspace.variable):
print("pfff it's a family")
self.params[key]['variable'] = variable
if suffix:
self.params[key]['suffix'] = suffix
else:
if not value.get('optional'):
raise Exception(f'pffff {value["variable"]}')
delete_keys.append(key)
elif value['type'] == 'information':
if 'variable' in value:
variable_path = self.get_realpath(value['variable'])
value['variable'] = self.objectspace.paths[variable_path]
for key in delete_keys:
del self.params[key]
def _jinja_to_function(self,
function,
return_type,
multi,
*,
add_help=False,
):
variable = self.objectspace.paths[self.path]
jinja_path = f'{self.attribute}_{self.path}'
idx = 0
while jinja_path in self.objectspace.jinja:
jinja_path = f'{self.attribute}_{self.path}_{idx}'
idx += 1
self.objectspace.jinja[jinja_path] = self.value
default = {'function': function,
'params': {'__internal_jinja': jinja_path,
'__internal_type': return_type,
'__internal_multi': multi,
}
}
if add_help:
default['help'] = function + '_help'
if self.params:
default['params'] |= self.params
for sub_variable, suffix, true_path in get_jinja_variable_to_param(self.value,
self.objectspace,
variable.xmlfiles,
self.objectspace.functions,
self.path_prefix,
):
if isinstance(sub_variable, self.objectspace.variable):
default['params'][true_path] = {'type': 'variable',
'variable': sub_variable,
}
if suffix:
default['params'][true_path]['suffix'] = suffix
return default
def to_property_function(self) -> dict:
self.change_variable_in_params()
if self.return_type:
raise Exception('return_type not allowed!')
if self.type == 'jinja':
if None in self.params:
self.params[None] = [self.attribute] + self.params[None]
else:
self.params[None] = [self.attribute]
return self._jinja_to_function('jinja_to_property',
'string',
False,
add_help=True,
)
if self.type == 'variable':
variable_path = self.get_realpath(self.value)
variable = self.objectspace.paths[variable_path]
if variable.type != 'boolean':
raise Exception('only boolean!')
print("**********", self.attribute)
default = {'function': 'variable_to_property',
'params': {None: [self.attribute, variable]},
# 'params': {None: [{'type': 'variable',
# 'variable': self.objectspace.paths[variable_path],
# 'propertyerror': self.propertyerror,
# }],
'help': 'variable_to_property',
}
print('pfff', default, self.path)
return default
raise Exception('pfff')
def to_default_function(self) -> dict:
self.change_variable_in_params()
if self.type == 'jinja':
if self.return_type:
return_type = self.return_type
else:
variable = self.objectspace.paths[self.path]
return_type = variable.type
if self.inside_list:
multi = False
elif self.path in self.objectspace.followers:
multi = self.objectspace.multis[self.path] == 'submulti'
else:
multi = self.path in self.objectspace.multis
return self._jinja_to_function('jinja_to_function',
return_type,
multi,
)
elif self.type == 'variable':
variable_path = self.get_realpath(self.value)
return {'function': 'calc_value',
'params': {None: [{'type': 'variable',
'variable': self.objectspace.paths[variable_path],
'propertyerror': self.propertyerror,
}],
'multi': self.path in self.objectspace.multis,
'allow_none': True,
}
}
elif self.type == 'suffix':
if self.params:
raise Exception('pfff')
variable_path = self.get_realpath(self.value)
return {'function': 'calc_value',
'params': {None: [{'type': 'suffix'}],
'multi': self.objectspace.paths[variable_path].multi,
}
}
else:
raise Exception('pfff')
def to_choice_function(self) -> dict:
self.change_variable_in_params()
return_type = self.return_type
if return_type is None:
return_type = 'string'
if self.type == 'jinja':
return self._jinja_to_function('jinja_to_function',
return_type,
not self.inside_list,
)
elif self.type == 'variable':
variable_path = self.get_realpath(self.value)
variable = self.objectspace.paths[variable_path]
if variable.path not in self.objectspace.multis:
raise Exception('pffff')
return {'function': 'calc_value',
'params': {None: [{'type': 'variable',
'variable': variable,
}],
'multi': False,
'allow_none': True,
}
}
else:
raise Exception('pfff')

View file

@ -1,10 +1 @@
{
"services.tata_service.activate": {
"owner": "default",
"value": true
},
"services.tata_service.manage": {
"owner": "default",
"value": true
}
}
{}

View file

@ -1,4 +1 @@
{
"services.tata_service.activate": true,
"services.tata_service.manage": true
}
{}

View file

@ -1,10 +1 @@
{
"services.tata_service.activate": {
"owner": "default",
"value": true
},
"services.tata_service.manage": {
"owner": "default",
"value": true
}
}
{}

View file

@ -1,7 +1,12 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("normal")
ALLOWED_LEADER_PROPERTIES.add("expert")
from importlib.machinery import SourceFileLoader as _SourceFileLoader
from importlib.util import spec_from_loader as _spec_from_loader, module_from_spec as _module_from_spec
class func:
pass
global func
func = {'calc_value': calc_value}
def _load_functions(path):
global _SourceFileLoader, _spec_from_loader, _module_from_spec, func
@ -12,20 +17,6 @@ def _load_functions(path):
for function in dir(func_):
if function.startswith('_'):
continue
setattr(func, function, getattr(func_, function))
func[function] = getattr(func_, function)
_load_functions('tests/dictionaries/../eosfunc/test.py')
try:
from tiramisu4 import *
from tiramisu4.setting import ALLOWED_LEADER_PROPERTIES
except:
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("normal")
ALLOWED_LEADER_PROPERTIES.add("expert")
option_1 = BoolOption(name="activate", doc="activate", default=True)
option_2 = BoolOption(name="manage", doc="manage", default=True)
optiondescription_4 = OptionDescription(name="tata_service", doc="tata.service", children=[option_1, option_2])
optiondescription_4.impl_set_information('type', "service")
optiondescription_3 = OptionDescription(name="services", doc="services", children=[optiondescription_4], properties=frozenset({"hidden"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_3])
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[])

View file

@ -1,7 +1,12 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("normal")
ALLOWED_LEADER_PROPERTIES.add("expert")
from importlib.machinery import SourceFileLoader as _SourceFileLoader
from importlib.util import spec_from_loader as _spec_from_loader, module_from_spec as _module_from_spec
class func:
pass
global func
func = {'calc_value': calc_value}
def _load_functions(path):
global _SourceFileLoader, _spec_from_loader, _module_from_spec, func
@ -12,27 +17,8 @@ def _load_functions(path):
for function in dir(func_):
if function.startswith('_'):
continue
setattr(func, function, getattr(func_, function))
func[function] = getattr(func_, function)
_load_functions('tests/dictionaries/../eosfunc/test.py')
try:
from tiramisu4 import *
from tiramisu4.setting import ALLOWED_LEADER_PROPERTIES
except:
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("normal")
ALLOWED_LEADER_PROPERTIES.add("expert")
option_1 = BoolOption(name="activate", doc="activate", default=True)
option_2 = BoolOption(name="manage", doc="manage", default=True)
optiondescription_7 = OptionDescription(name="tata_service", doc="tata.service", children=[option_1, option_2])
optiondescription_7.impl_set_information('type', "service")
optiondescription_6 = OptionDescription(name="services", doc="services", children=[optiondescription_7], properties=frozenset({"hidden"}))
optiondescription_5 = OptionDescription(name="1", doc="1", children=[optiondescription_6])
option_3 = BoolOption(name="activate", doc="activate", default=True)
option_4 = BoolOption(name="manage", doc="manage", default=True)
optiondescription_10 = OptionDescription(name="tata_service", doc="tata.service", children=[option_3, option_4])
optiondescription_10.impl_set_information('type', "service")
optiondescription_9 = OptionDescription(name="services", doc="services", children=[optiondescription_10], properties=frozenset({"hidden"}))
optiondescription_8 = OptionDescription(name="2", doc="2", children=[optiondescription_9])
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_5, optiondescription_8])
optiondescription_1 = OptionDescription(name="1", doc="1", children=[], properties=frozenset({"expert"}))
optiondescription_2 = OptionDescription(name="2", doc="2", children=[], properties=frozenset({"expert"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_2])

View file

@ -0,0 +1,3 @@
version: '1.0'
my_family:

View file

@ -0,0 +1,3 @@
version: '1.0'
my_family:
my_sub_family:

View file

@ -0,0 +1,3 @@
---
version: '1.0'
empty:

View file

@ -0,0 +1,6 @@
{
"rougail.empty": {
"owner": "default",
"value": null
}
}

View file

@ -0,0 +1,3 @@
{
"rougail.empty": null
}

View file

@ -0,0 +1,6 @@
{
"rougail.empty": {
"owner": "default",
"value": null
}
}

View file

@ -0,0 +1,24 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("normal")
ALLOWED_LEADER_PROPERTIES.add("expert")
from importlib.machinery import SourceFileLoader as _SourceFileLoader
from importlib.util import spec_from_loader as _spec_from_loader, module_from_spec as _module_from_spec
global func
func = {'calc_value': calc_value}
def _load_functions(path):
global _SourceFileLoader, _spec_from_loader, _module_from_spec, func
loader = _SourceFileLoader('func', path)
spec = _spec_from_loader(loader.name, loader)
func_ = _module_from_spec(spec)
loader.exec_module(func_)
for function in dir(func_):
if function.startswith('_'):
continue
func[function] = getattr(func_, function)
_load_functions('tests/dictionaries/../eosfunc/test.py')
option_2 = StrOption(name="empty", doc="empty", properties=frozenset({"normal"}))
optiondescription_1 = OptionDescription(name="rougail", doc="rougail", children=[option_2], properties=frozenset({"normal"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,28 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("normal")
ALLOWED_LEADER_PROPERTIES.add("expert")
from importlib.machinery import SourceFileLoader as _SourceFileLoader
from importlib.util import spec_from_loader as _spec_from_loader, module_from_spec as _module_from_spec
global func
func = {'calc_value': calc_value}
def _load_functions(path):
global _SourceFileLoader, _spec_from_loader, _module_from_spec, func
loader = _SourceFileLoader('func', path)
spec = _spec_from_loader(loader.name, loader)
func_ = _module_from_spec(spec)
loader.exec_module(func_)
for function in dir(func_):
if function.startswith('_'):
continue
func[function] = getattr(func_, function)
_load_functions('tests/dictionaries/../eosfunc/test.py')
option_3 = StrOption(name="empty", doc="empty", properties=frozenset({"normal"}))
optiondescription_2 = OptionDescription(name="rougail", doc="rougail", children=[option_3], properties=frozenset({"normal"}))
optiondescription_1 = OptionDescription(name="1", doc="1", children=[optiondescription_2], properties=frozenset({"normal"}))
option_6 = StrOption(name="empty", doc="empty", properties=frozenset({"normal"}))
optiondescription_5 = OptionDescription(name="rougail", doc="rougail", children=[option_6], properties=frozenset({"normal"}))
optiondescription_4 = OptionDescription(name="2", doc="2", children=[optiondescription_5], properties=frozenset({"normal"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_4])

View file

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<rougail version="0.10">
<services>
<service name="tata">
</service>
</services>
</rougail>

View file

@ -1,10 +0,0 @@
{
"rougail.myvar": {
"owner": "forced",
"value": "no"
},
"rougail.server_deployed": {
"owner": "default",
"value": false
}
}

View file

@ -1,4 +0,0 @@
{
"rougail.myvar": "no",
"rougail.server_deployed": false
}

View file

@ -1,10 +0,0 @@
{
"rougail.myvar": {
"owner": "default",
"value": "no"
},
"rougail.server_deployed": {
"owner": "default",
"value": false
}
}

View file

@ -1,29 +0,0 @@
from importlib.machinery import SourceFileLoader as _SourceFileLoader
from importlib.util import spec_from_loader as _spec_from_loader, module_from_spec as _module_from_spec
class func:
pass
def _load_functions(path):
global _SourceFileLoader, _spec_from_loader, _module_from_spec, func
loader = _SourceFileLoader('func', path)
spec = _spec_from_loader(loader.name, loader)
func_ = _module_from_spec(spec)
loader.exec_module(func_)
for function in dir(func_):
if function.startswith('_'):
continue
setattr(func, function, getattr(func_, function))
_load_functions('tests/dictionaries/../eosfunc/test.py')
try:
from tiramisu4 import *
from tiramisu4.setting import ALLOWED_LEADER_PROPERTIES
except:
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("normal")
ALLOWED_LEADER_PROPERTIES.add("expert")
option_2 = BoolOption(name="server_deployed", doc="server_deployed", default=False, properties=frozenset({"mandatory", "normal"}))
option_1 = StrOption(name="myvar", doc="myvar", default="no", properties=frozenset({"basic", "force_store_value", "mandatory", Calculation(func.calc_value, Params(ParamValue('frozen'), kwargs={'condition': ParamOption(option_2, notraisepropertyerror=True), 'expected': ParamValue(True)}), func.calc_value_property_help)}))
optiondescription_3 = OptionDescription(name="rougail", doc="Rougail", children=[option_1, option_2], properties=frozenset({"basic"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_3])

View file

@ -1,34 +0,0 @@
from importlib.machinery import SourceFileLoader as _SourceFileLoader
from importlib.util import spec_from_loader as _spec_from_loader, module_from_spec as _module_from_spec
class func:
pass
def _load_functions(path):
global _SourceFileLoader, _spec_from_loader, _module_from_spec, func
loader = _SourceFileLoader('func', path)
spec = _spec_from_loader(loader.name, loader)
func_ = _module_from_spec(spec)
loader.exec_module(func_)
for function in dir(func_):
if function.startswith('_'):
continue
setattr(func, function, getattr(func_, function))
_load_functions('tests/dictionaries/../eosfunc/test.py')
try:
from tiramisu4 import *
from tiramisu4.setting import ALLOWED_LEADER_PROPERTIES
except:
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("normal")
ALLOWED_LEADER_PROPERTIES.add("expert")
option_2 = BoolOption(name="server_deployed", doc="server_deployed", default=False, properties=frozenset({"mandatory", "normal"}))
option_1 = StrOption(name="myvar", doc="myvar", default="no", properties=frozenset({"basic", "force_store_value", "mandatory", Calculation(func.calc_value, Params(ParamValue('frozen'), kwargs={'condition': ParamOption(option_2, notraisepropertyerror=True), 'expected': ParamValue(True)}), func.calc_value_property_help)}))
optiondescription_6 = OptionDescription(name="rougail", doc="Rougail", children=[option_1, option_2], properties=frozenset({"basic"}))
optiondescription_5 = OptionDescription(name="1", doc="1", children=[optiondescription_6])
option_4 = BoolOption(name="server_deployed", doc="server_deployed", default=False, properties=frozenset({"mandatory", "normal"}))
option_3 = StrOption(name="myvar", doc="myvar", default="no", properties=frozenset({"basic", "force_store_value", "mandatory", Calculation(func.calc_value, Params(ParamValue('frozen'), kwargs={'condition': ParamOption(option_4, notraisepropertyerror=True), 'expected': ParamValue(True)}), func.calc_value_property_help)}))
optiondescription_8 = OptionDescription(name="rougail", doc="Rougail", children=[option_3, option_4], properties=frozenset({"basic"}))
optiondescription_7 = OptionDescription(name="2", doc="2", children=[optiondescription_8])
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_5, optiondescription_7])

View file

@ -1,11 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?>
<rougail version="0.10">
<variables>
<variable name="myvar" auto_freeze="True">
<value>no</value>
</variable>
<variable name="server_deployed" type="boolean">
<value>False</value>
</variable>
</variables>
</rougail>

View file

@ -1,11 +0,0 @@
version: '0.10'
variables:
- variable:
- name: myvar
auto_freeze: true
value:
- text: 'no'
- name: server_deployed
type: boolean
value:
- text: false

View file

@ -1,10 +0,0 @@
{
"rougail.my_var": {
"owner": "forced",
"value": "no"
},
"rougail.server_deployed": {
"owner": "default",
"value": false
}
}

View file

@ -1,4 +0,0 @@
{
"rougail.my_var": "no",
"rougail.server_deployed": false
}

Some files were not shown because too many files have changed in this diff Show more