Compare commits
176 commits
main
...
docs_updat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d47405c61d | ||
| 8b02c79dcc | |||
|
|
aabc937d1c | ||
|
|
fe4e98624a | ||
|
|
c850c11e98 | ||
|
|
01c4325b86 | ||
|
|
01d99cbd01 | ||
|
|
0a10dfbd7d | ||
|
|
b437572de9 | ||
|
|
921fc0f481 | ||
|
|
a9b6a00087 | ||
|
|
d9ac902740 | ||
|
|
b22c1817b9 | ||
|
|
8e2fcd073d | ||
|
|
9413831037 | ||
|
|
9d5ca18bdc | ||
|
|
8bc7c9b04d | ||
|
|
40df27bf90 | ||
|
|
65d4440694 | ||
|
|
be4983e524 | ||
|
|
920780384d | ||
|
|
e61ff106eb | ||
|
|
cb9b534669 | ||
|
|
50bf8e696d | ||
|
|
76e59b22dd | ||
|
|
4057ef6fc7 | ||
|
|
f1af25d11a | ||
|
|
9ca4f31986 | ||
|
|
b5c919dc8d | ||
|
|
1364b3b4f2 | ||
|
|
2e99a30e38 | ||
| 1cd89747de | |||
|
|
22c7472297 | ||
|
|
778a8f5e48 | ||
|
|
7980e9f034 | ||
|
|
dbe78f2334 | ||
|
|
7fc56b2429 | ||
|
|
4da1408c7f | ||
|
|
99394348b5 | ||
|
|
cb52c774be | ||
|
|
2b0dd5e5bd | ||
| 9c2885ce94 | |||
| d3bfa99ee4 | |||
| 543d33a29b | |||
| 6c210f3c33 | |||
| 1adae63e18 | |||
|
|
9d6778ce4f | ||
|
|
f0018fcf93 | ||
|
|
043038526d | ||
|
|
20b90f7c70 | ||
|
|
9d67d93107 | ||
|
|
947ae2a029 | ||
|
|
55213e18db | ||
|
|
638442273b | ||
|
|
a0e556e7c1 | ||
| aca97a3e92 | |||
| bc0de749c7 | |||
|
|
9961d1c958 | ||
|
|
8912e6dd63 | ||
|
|
f3c3fe4df0 | ||
|
|
c378fbbf89 | ||
|
|
82b934db02 | ||
|
|
7f4efef0be | ||
|
|
e511f07932 | ||
|
|
e64a89990d | ||
|
|
5cc1e50669 | ||
|
|
7a20515114 | ||
| 5b1356970f | |||
| bc415deba9 | |||
|
|
6a87c23e2b | ||
|
|
42d5762a5f | ||
|
|
22a5621c5d | ||
| 9d856f9de0 | |||
| 2eeedd70e2 | |||
| 4bb50361d5 | |||
| 360c97c581 | |||
| 1277cce9d9 | |||
| 96975461d0 | |||
|
|
961cee372e | ||
|
|
16af68e9c8 | ||
|
|
6505ac9ab9 | ||
|
|
6b6aeceb6e | ||
|
|
91112a905a | ||
|
|
1da10b7970 | ||
|
|
15bd2c4986 | ||
|
|
8a7959ed11 | ||
|
|
ad80093520 | ||
|
|
7e163e56f0 | ||
|
|
f7701917d0 | ||
|
|
a3d7258a64 | ||
|
|
c2e8e3fd5a | ||
|
|
8d7dde7bcc | ||
|
|
ad2c9cf2ce | ||
|
|
0293d4d455 | ||
|
|
dd0ae7d03b | ||
|
|
e49f731aa2 | ||
|
|
284a176a0a | ||
|
|
edde6e2a85 | ||
|
|
3aed61cdb1 | ||
|
|
4af0e7cbe2 | ||
|
|
935b33d28e | ||
|
|
91ffd19336 | ||
|
|
fce54f7c84 | ||
|
|
cfb6017f32 | ||
|
|
a932d7e1c3 | ||
|
|
2da85eb0e8 | ||
|
|
9ca27d7f51 | ||
|
|
2d39fe8bad | ||
|
|
10a25b0cfe | ||
|
|
0f0d955e9f | ||
|
|
038cd9429a | ||
|
|
cc54cf675f | ||
|
|
b4efbf1d71 | ||
|
|
28fe83cfb2 | ||
|
|
6edda03d79 | ||
|
|
55bda5cd8c | ||
|
|
543d54144b | ||
|
|
98f788ee9b | ||
|
|
bf6120f95e | ||
|
|
04f30963f4 | ||
|
|
eda0632809 | ||
|
|
c2111fe581 | ||
|
|
9870d949b9 | ||
|
|
5dc7d2b630 | ||
|
|
fbc0744f61 | ||
|
|
d7b2e9b521 | ||
|
|
5cf4fb16a4 | ||
|
|
a0546a137f | ||
|
|
b1ed2d3aaf | ||
|
|
39f01b36a1 | ||
|
|
c361629b63 | ||
|
|
45b9a5495e | ||
|
|
19ecc56316 | ||
|
|
0ce1550028 | ||
|
|
cd850f1089 | ||
|
|
a310b6f063 | ||
|
|
50299203af | ||
|
|
080bb9c489 | ||
|
|
d47011efe4 | ||
|
|
9a84e28765 | ||
|
|
c244a816f3 | ||
|
|
9199631ea5 | ||
| 2d02da9939 | |||
| 06bb8f8fad | |||
| e2eb58664a | |||
| 2c0763c516 | |||
| ccb4eff30b | |||
| 7bdc39e370 | |||
| ea93bc55fc | |||
| ec86795768 | |||
| 810d08822b | |||
| 83840e329c | |||
| 1389929371 | |||
| b21c13fea7 | |||
| a827ca2225 | |||
| 75778cb39e | |||
| 9e614eb7a4 | |||
| 90ef65dd40 | |||
|
|
fd5b702686 | ||
| d4167b1586 | |||
| 161985ab50 | |||
| 908356495b | |||
| eb561f5f52 | |||
| 539ecc7412 | |||
|
|
b39b728a90 | ||
|
|
80e3b73785 | ||
|
|
a7c862cccd | ||
| e2d62211ec | |||
| 9705830e88 | |||
| 0a9d1b3014 | |||
|
|
4f33b7c7af | ||
| bdd56d31c8 | |||
|
|
6f9a981da7 | ||
|
|
4c6f451045 | ||
|
|
fc8c1ecfda | ||
|
|
7f7fe5f08f |
47
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.3.0
|
||||
hooks:
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- id: check-ast
|
||||
- id: check-added-large-files
|
||||
- id: check-json
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-symlinks
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
# - repo: https://github.com/hhatto/autopep8
|
||||
# rev: v2.0.4
|
||||
# hooks:
|
||||
# - id: autopep8
|
||||
|
||||
# - repo: https://github.com/pre-commit/mirrors-mypy
|
||||
# rev: v1.6.1
|
||||
# hooks:
|
||||
# - id: mypy
|
||||
|
||||
# - repo: https://github.com/PyCQA/pylint
|
||||
# rev: v3.0.2
|
||||
# hooks:
|
||||
# - id: pylint
|
||||
|
||||
# - repo: https://github.com/PyCQA/isort
|
||||
# rev: 5.11.5
|
||||
# hooks:
|
||||
# - id: isort
|
||||
|
||||
# - repo: https://github.com/motet-a/jinjalint
|
||||
# rev: 0.5
|
||||
# hooks:
|
||||
# - id: jinjalint
|
||||
|
||||
# - repo: https://github.com/rstcheck/rstcheck
|
||||
# rev: v6.2.0
|
||||
# hooks:
|
||||
# - id: rstcheck
|
||||
937
caracteristique.md
Normal file
|
|
@ -0,0 +1,937 @@
|
|||
---
|
||||
gitea: none
|
||||
include_toc: true
|
||||
---
|
||||
|
||||
# Rougail : caractéristiques
|
||||
|
||||
Rougail est un outil qui permet de gérer le cycle de vie des variables et de gestion des valeurs de ces variables.
|
||||
|
||||
Au moment de la conception de Rougail, il y a eu des choix structurant qui ont défini le fonctionnement de l'outil.
|
||||
|
||||
Voici la liste des principales caractéristiques :
|
||||
|
||||
|
||||
## Logiciel libre
|
||||
|
||||
Rougail est un logiciel libre (et Open Source) de gestion externe de variables.
|
||||
Le développement est réalisé de manière ouverte.
|
||||
|
||||
Rougail repose sur le logiciel libre Tiramisu comme moteur de contrainte.
|
||||
|
||||
Rougail est composé :
|
||||
|
||||
- d'un format de description
|
||||
- d'un projet principal "Rougail" qui :
|
||||
|
||||
- charge les fichiers de structures,
|
||||
- applique les valeurs des données utilisateur,
|
||||
- représente les variables avec leurs valeurs.
|
||||
|
||||
- différents sous projet pour étendre les fonctionnalités de base
|
||||
- un outil en ligne de commande pour facilité l'utilisation de la bibliothèque.
|
||||
|
||||
|
||||
## Les acteurs
|
||||
|
||||
Rougail est destiné a séparer le cycle de vie de la variable entre 3 acteurs :
|
||||
|
||||
- l'acteur qui défini les variables
|
||||
- l'acteur qui adapte les valeurs
|
||||
- l'acteur qui utilise les variables avec leurs valeurs
|
||||
|
||||
Exemples concret d'acteur :
|
||||
|
||||
Dans le cadre du déploiement d'une configuration :
|
||||
|
||||
- l'intégrateur défini les variables
|
||||
- l'exploitant adapte les valeurs
|
||||
- l'installeur utilise les variables avec leurs valeurs
|
||||
|
||||
Dans le cadre du développement d'une application :
|
||||
|
||||
- le dévelopeur défini les variables
|
||||
- l'utilisateur adapte les valeurs
|
||||
- l'application utilise les variables avec leurs valeurs
|
||||
|
||||
|
||||
|------------------------------------------|
|
||||
| Acteur |
|
||||
|------------------------------------------|
|
||||
| défini les variables |
|
||||
| adapte les valeurs |
|
||||
| utilise les variables avec leurs valeurs |
|
||||
|------------------------------------------|
|
||||
|
||||
|
||||
## Cycle de vie des variables
|
||||
|
||||
Le but de Rougail est de gérer les variables et plus largement la configuration.
|
||||
|
||||
Rougail a pour but de définir les variables puis de gérer tout son cycle de vie.
|
||||
|
||||
Dans le cycle de vie d'une variable, on inclut les étapes générique d'une variable (par exemple pour le langage C) :
|
||||
|
||||
- déclaration (elles se voient attribuer un nom et un type) ;
|
||||
- définition (elles se voient affecter leur première valeur) ;
|
||||
- affectation (elles se voient modifier la valeur de la variable) ;
|
||||
- lecture (elles se voient utilisent la valeur de la variable) ;
|
||||
- fin de vie (elles se terminent à la destruction de l'objet).
|
||||
|
||||
Mais d'autres notions sont inclusent dans le cycle de vie :
|
||||
|
||||
- extension de la déclaration
|
||||
|
||||
- la "description" de la variable
|
||||
- présentation, l'acteur qui adapte les valeurs doit avoir toutes les informations nécessaires pour renseigner les valeurs, c'est ainsi qu'il est possible de généré automatiquement la documentation, le journal des modifications, ...
|
||||
|
||||
- autorisations, des propriétés décrivent les contraintes d'accès
|
||||
- spécialisation, qui décrit l'usage possible d'une variable
|
||||
|
||||
Rôle des différents composants Rougail :
|
||||
|
||||
Le format permet de décrire :
|
||||
|
||||
- la déclaration (nom, type, description, la présentation)
|
||||
- les autorisations
|
||||
- la spécialisation
|
||||
|
||||
Rougail et ces composants :
|
||||
|
||||
- la déclaration (via Tiramisu)
|
||||
- la présentation
|
||||
- l'affectation (via Tiramisu)
|
||||
- la lecture
|
||||
- la fin de vie
|
||||
|
||||
|------------------------------------------|----------------------------|
|
||||
| Acteur | Cycle de vies |
|
||||
|------------------------------------------|----------------------------|
|
||||
| défini les variables | déclaration + présentation |
|
||||
| adapte les valeurs | affectration |
|
||||
| utilise les variables avec leurs valeurs | lecture |
|
||||
| | destruction |
|
||||
|------------------------------------------|----------------------------|
|
||||
|
||||
|
||||
## Architecture Structurals-UserDatas-Outputs
|
||||
|
||||
|
||||
### Structurals
|
||||
|
||||
Les fichiers de "Structures" sont des fichiers au format Rougail dans laquelle toutes les informations du cycle de vie sont définies.
|
||||
Il est important de tout mettre ces informations au même endroit. Ainsi on connaitre toutes les aspects de la variable en lisant ces fichiers.
|
||||
On y retrouve :
|
||||
|
||||
- un schéma
|
||||
- la définition des contraintes
|
||||
- des éléments de documentation
|
||||
- ...
|
||||
|
||||
Les variables sont ici mutables, elles peuvent être redéfinit à tout moment.
|
||||
Ces fichiers sont créées par l'acteur qui défini les variables.
|
||||
|
||||
|
||||
### UserDatas
|
||||
|
||||
Une fois la structure définie, il est possible de charger les "Données Utilisateur" (UserDatas). On retrouve plusieurs type de données utilisateurs :
|
||||
|
||||
- des fichiers de configuration
|
||||
- des variables d'environnement
|
||||
- des sources externes
|
||||
- des options de lignes de commande
|
||||
- un formulaire
|
||||
- ...
|
||||
|
||||
Les variables sont ici immuables, par contre les valeurs sont mutables.
|
||||
Ces sources sont peuplées par l'acteur qui adapte les valeurs.
|
||||
|
||||
|
||||
### Outputs
|
||||
|
||||
Ensuite on pourra définir sous quelle forme on veut recueillir l'information (Outputs) :
|
||||
|
||||
- un objet Tiramisu
|
||||
- une extraction JSON
|
||||
- un export pour l'inventaire Ansible
|
||||
- de la documetation
|
||||
- ...
|
||||
|
||||
Les variables et les valeurs sont ici immuables.
|
||||
|
||||
Voici un exemple concret :
|
||||
|
||||
Un intégrateur a besoin d'une variable et défini la défini ainsi (dans le fichier `structure_architecture.yml`) :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable:
|
||||
...
|
||||
```
|
||||
|
||||
L'exploitant défini la valeur de cette variable ainsi (dans le fichier `userdata.yml`) :
|
||||
|
||||
```yaml
|
||||
---
|
||||
my_variable: a value
|
||||
```
|
||||
|
||||
On désire afficher de manière lisible la configuration :
|
||||
|
||||
```bash
|
||||
rougail -m structure_architecture.yml -u yaml -yf userdata.yml
|
||||
╭──────── Caption ────────╮
|
||||
│ Variable Modified value │
|
||||
╰─────────────────────────╯
|
||||
Variables:
|
||||
┗━━ 📓 my_variable: a value ◀ loaded from the YAML file "userdata.yml"
|
||||
```
|
||||
|
||||
|-------------|------------------------------------------|----------------------------|----------|----------|
|
||||
| Étape | Acteur | Cycle de vies | Variable | Valeur |
|
||||
|-------------|------------------------------------------|----------------------------|----------|----------|
|
||||
| Structurals | défini les variables | déclaration + présentation | mutable | |
|
||||
| UserDatas | adapte les valeurs | affectration | immuable | mutable |
|
||||
| Outputs | utilise les variables avec leurs valeurs | lecture | immuable | immuable |
|
||||
| | | destruction | | |
|
||||
|-------------|------------------------------------------|----------------------------|----------|----------|
|
||||
|
||||
|
||||
### Valeur par défaut
|
||||
|
||||
Les variables ont une valeur. Une valeur par défaut est définit à la variable (None ou []) mais il est possible d'en définir une autre.
|
||||
|
||||
Il ne faut pas confondre la valeur par défaut ou la/les valeur(s) défini par l'acteur adaptant la configuration.
|
||||
|
||||
Par exemple, si je veux définir la variable my_variable en y spécifiant une valeur par défaut (dans le fichier `structure_default.yml`) :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable:
|
||||
default: a default value
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
rougail -m structure_default.yml
|
||||
╭─────── Caption ────────╮
|
||||
│ Variable Default value │
|
||||
╰────────────────────────╯
|
||||
Variables:
|
||||
┗━━ 📓 my_variable: a default value
|
||||
```
|
||||
|
||||
|-------------|------------------------------------------|----------------------------|----------|----------|
|
||||
| Étape | Acteur | Cycle de vies | Variable | Valeur |
|
||||
|-------------|------------------------------------------|----------------------------|----------|----------|
|
||||
| Structurals | défini les variables | déclaration + présentation | mutable | défaut |
|
||||
| UserDatas | adapte les valeurs | affectration | immuable | mutable |
|
||||
| Outputs | utilise les variables avec leurs valeurs | lecture | immuable | immuable |
|
||||
| | | destruction | | |
|
||||
|-------------|------------------------------------------|----------------------------|----------|----------|
|
||||
|
||||
|
||||
## Le format : un langage
|
||||
|
||||
|
||||
### DSL (Domain Specific Language)
|
||||
|
||||
Contrairement à un langage générique, un langage dédié est conçu pour répondre à un domaine d'application précis.
|
||||
|
||||
La description des variables se faire dans des fichiers YAML version 1.2. Le langage de configuration de Rougail permet de décrire tout le cycle de vie de la variable.
|
||||
|
||||
|
||||
### Abstraction déclarative
|
||||
|
||||
Les variables sont décrite suivant un modèle déclaratif. Cela signigie que l'utilisateur définit simplement l'état final souhaité de celle-ci. Tiramisu determinera les actions nécessaires pour atteindre cet état.
|
||||
|
||||
Dit autrement l'utilisateur défini le schéma des variables qui sera ensuite appliqué de manière déterministe.
|
||||
|
||||
|
||||
|
||||
### Redéfinition explicite
|
||||
|
||||
Les variables peuvent être redéfinis à tout moment (utile notamment lorsqu'on définit des modèles de configuration). Mais la redéfinition d'une variable doit être explicitement déclaré comme tel.
|
||||
|
||||
Par exemple, si je veux redéfinir la variable my_variable en y spécifiant une valeur par défaut (dans le fichier `structure_redefine.yml`) :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable:
|
||||
redefine: true
|
||||
default: a new default value
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
rougail -m structure_default.yml structure_redefine.yml
|
||||
╭─────── Caption ────────╮
|
||||
│ Variable Default value │
|
||||
╰────────────────────────╯
|
||||
Variables:
|
||||
┗━━ 📓 my_variable: a new default value
|
||||
```
|
||||
|
||||
|
||||
## Typage
|
||||
|
||||
|
||||
### Type standard
|
||||
|
||||
Rougail (et Tiramisu) accepte les types standards suivant :
|
||||
|
||||
- string (type par défaut d'une variable)
|
||||
- integer
|
||||
- float
|
||||
- boolean
|
||||
|
||||
|
||||
### Type métier
|
||||
|
||||
Mais on va retrouver également tout une série de type métier :
|
||||
|
||||
- IP
|
||||
- domainname
|
||||
- port
|
||||
- MAC
|
||||
- choice
|
||||
- secret
|
||||
- ...
|
||||
|
||||
Voici quelques exemples (dans le fichier `structure_type.yml`) :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_integer: 1
|
||||
|
||||
my_mail:
|
||||
type: mail
|
||||
default: foo@bar.net
|
||||
|
||||
my_date:
|
||||
type: date
|
||||
default: "2025-11-19"
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
rougail -m structure_type.yml
|
||||
╭─────── Caption ────────╮
|
||||
│ Variable Default value │
|
||||
╰────────────────────────╯
|
||||
Variables:
|
||||
┣━━ 📓 my_integer: 1
|
||||
┣━━ 📓 my_mail: foo@bar.net
|
||||
┗━━ 📓 my_date: 2025-11-19
|
||||
```
|
||||
|
||||
|
||||
### Fortement typé
|
||||
|
||||
Rougail utilise en interne la bibliothèque Tiramisu. Les variables dans Tiramisu sont fortement typé.
|
||||
C'est à dire que le chargement des données utilisateur implique une attention sur le type des variables. Pour les données utilisateurs non typées (comme les variables d'environnement), en pré traitement, il y aura une adaptation du type de la valeur.
|
||||
|
||||
Par exemple si l'exploitant adapte la valeur de cette variable ainsi (dans le fichier userdata_fort_type.yml :
|
||||
|
||||
```yaml
|
||||
---
|
||||
my_variable: 1
|
||||
```
|
||||
|
||||
La valeur ne pourra pas être chargée (le type par défaut étant le type "string") :
|
||||
|
||||
```bash
|
||||
rougail -m structure_architecture.yml -u yaml -yf userdata_fort_type.yml
|
||||
🔔 WARNINGS
|
||||
┗━━ the value "1" is an invalid string for "my_variable", which is not a string, it will be ignored when loading from the YAML file "userdata.yml"
|
||||
🛑 ERRORS
|
||||
┗━━ The following variables are mandatory but have no value:
|
||||
┗━━ my_variable
|
||||
```
|
||||
|
||||
### Typage dynamique
|
||||
|
||||
Au moment de la définition de la structure, le type est dynamique. C'est à dire que l'acteur qui défini la variable peut changé à tout moment le type de la variable.
|
||||
|
||||
Par exemple (dans le fichier `structure_redefine_type.yml`) :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable:
|
||||
redefine: true
|
||||
type: integer
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
rougail -m structure_architecture.yml structure_redefine_type.yml -u yaml -yf userdata_fort_type.yml
|
||||
╭──────── Caption ────────╮
|
||||
│ Variable Modified value │
|
||||
╰─────────────────────────╯
|
||||
Variables:
|
||||
┗━━ 📓 my_variable: 1 ◀ loaded from the YAML file "userdata.yml"
|
||||
```
|
||||
|
||||
Par contre, comme l'exemple du typage fort le suggère, l'acteur qui adapte la valeur n'a pas la possiblité de redéfinir le type de la variable.
|
||||
|
||||
### Inférence de type
|
||||
|
||||
Le type peut être défini explicitement (comme dans le fichier structure_type.yml) ou déduit du typage des variables YAML.
|
||||
|
||||
Par exemple la variable avec une valeur par défaut à 1 est une variable de type "integer" (a mettre dans le fichier `structure_inference.yml`) :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable: 1
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
rougail -m structure_inference.yml
|
||||
╭─────── Caption ────────╮
|
||||
│ Variable Default value │
|
||||
╰────────────────────────╯
|
||||
Variables:
|
||||
┗━━ 📓 my_variable: 1
|
||||
```
|
||||
|
||||
|
||||
### Variable "nullable"
|
||||
|
||||
Le type "null" (ou "None" en python) n'existe pas dans Rougail. "null" est une valeur. Tous les types peuvent accepter cette valeur, mais par défaut, ce n'est pas le cas.
|
||||
|
||||
Voici la déclaration d'une variable avec la valeur par défaut à "null" (dans le fichier `structure_nullable.yml`) :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable:
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
rougail -m structure_nullable.yml
|
||||
🛑 ERRORS
|
||||
┗━━ The following variables are mandatory but have no value:
|
||||
┗━━ my_variable
|
||||
```
|
||||
|
||||
En réalité la variable n'est pas accessible lorsque Tiramisu est mode "lecture seule" (ce qui est le cas lors des étapes Outputs par défaut). Lorsqu'on force le mode "lecture écriture" on a bien accès :
|
||||
|
||||
```bash
|
||||
rougail -m structure_nullable.yml --cli.read_write
|
||||
╭─────── Caption ────────╮
|
||||
│ Variable Default value │
|
||||
╰────────────────────────╯
|
||||
Variables:
|
||||
┗━━ 📓 my_variable: null
|
||||
```
|
||||
|
||||
Pour que notre variable accepte dans tous les cas "null" il faut modifier le fichier de structure comme cela :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable:
|
||||
mandatory: false
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
rougail -m structure_nullable.yml --cli.read_write
|
||||
╭─────── Caption ────────╮
|
||||
│ Variable Default value │
|
||||
╰────────────────────────╯
|
||||
Variables:
|
||||
┗━━ 📓 my_variable: null
|
||||
```
|
||||
|
||||
|
||||
### Variable "liste"
|
||||
|
||||
La liste n'est pas non plus un type. C'est une propriété d'une variable. Cela signifie qu'une liste ne peut pas contenir des valeurs de plusieurs types.
|
||||
|
||||
|
||||
### Famille objet
|
||||
|
||||
Une famille est une variable d'un type particuiler. C'est un conteneur destiné à accueillir des variables.
|
||||
|
||||
|
||||
#### Un objet
|
||||
|
||||
Ce qu'on appele "objet" généralement est appeler dans Rougail des "familles". Donc au lieu de déclarer mes variables à la racine, je vais la déclarer dans une famille.
|
||||
|
||||
Par exemple dans le fichier `structure_family.yml` je créé un famille my_object qui contient deux variables :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_object:
|
||||
|
||||
key1: value1
|
||||
|
||||
key2: value2
|
||||
...
|
||||
```
|
||||
|
||||
Si j'exporte au format JSON j'ai bien un objet :
|
||||
|
||||
```bash
|
||||
rougail -m structure_family.yml -o json
|
||||
{
|
||||
"my_object": {
|
||||
"key1": "value1",
|
||||
"key2": "value2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Les familles gèrent l'arborescence. Il est possible de faire des sous-familles.
|
||||
|
||||
|
||||
#### Liste d'objet
|
||||
|
||||
Une famille particulière, appeler "leadership" permet d'avoir une liste d'objet identique.
|
||||
|
||||
Par exemple si je veux pouvoir créer un nombre non limité d'utilisateur associé à un mot de passe, je ne peux pas passer par des listes, je veux une liste d'objet.
|
||||
|
||||
Voici le contenu du fichier `structure_leadership.yml` :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
users:
|
||||
type: leadership
|
||||
|
||||
username:
|
||||
type: unix_user
|
||||
|
||||
password:
|
||||
type: secret
|
||||
...
|
||||
```
|
||||
|
||||
Et le fichier `userdata_leadership.yml` :
|
||||
|
||||
```yaml
|
||||
---
|
||||
users:
|
||||
- username: foo
|
||||
password: SoSecr31
|
||||
- username: bar
|
||||
password: SoSecr31
|
||||
- username: toot
|
||||
password: SoSecr31
|
||||
...
|
||||
```
|
||||
|
||||
J'ai bien une liste d'objet :
|
||||
|
||||
```bash
|
||||
rougail -m structure_leadership.yml -u yaml -yf userdata_leadership.yml -o json
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"username": "foo",
|
||||
"password": "SoSecr31"
|
||||
},
|
||||
{
|
||||
"username": "bar",
|
||||
"password": "SoSecr31"
|
||||
},
|
||||
{
|
||||
"username": "toot",
|
||||
"password": "SoSecr31"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|-------------|------------------------------------------|----------------------------|----------|----------|------------|
|
||||
| Étape | Acteur | Cycle de vies | Variable | Valeur | Propriétés |
|
||||
|-------------|------------------------------------------|----------------------------|----------|----------|------------|
|
||||
| Structurals | défini les variables | déclaration + présentation | mutable | défaut | |
|
||||
| UserDatas | adapte les valeurs | affectration | immuable | mutable | |
|
||||
| Outputs | utilise les variables avec leurs valeurs | lecture | immuable | immuable | mandatory |
|
||||
| | | destruction | | | |
|
||||
|-------------|------------------------------------------|----------------------------|----------|----------|------------|
|
||||
|
||||
|
||||
## Intégrité des données
|
||||
|
||||
L'intégrité des données fait référence au fait que les données doivent être fiables et précises tout au long de leur cycle de vie.
|
||||
|
||||
Cela signifie que la valeur doit être :
|
||||
|
||||
- de qualité
|
||||
- adapté au context globale
|
||||
|
||||
|
||||
### Qualité de la donnée
|
||||
|
||||
Pour avoir des données de qualité, il faut que l'outil valide la saisie utilisateur.
|
||||
|
||||
La première façon de valider la donnée est bien évidement de définir le bon type.
|
||||
|
||||
Mais cela ne suffit pas.
|
||||
|
||||
#### Paramètres de type
|
||||
|
||||
Il existe, pour certains type, un certains nombres de paramètres qui vont pouvoir compléter le typage des variables (dans le fichier `structure_param_type.yml`) :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_percent:
|
||||
type: integer
|
||||
params:
|
||||
min_integer: 0
|
||||
max_integer: 100
|
||||
default:
|
||||
10
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
rougail -m structure_param_type.yml
|
||||
╭─────── Caption ────────╮
|
||||
│ Variable Default value │
|
||||
╰────────────────────────╯
|
||||
Variables:
|
||||
┗━━ 📓 my_percent: 10
|
||||
```
|
||||
|
||||
Mais avec une donnée utilisateur invalide, la valeur ne sera pas chargé (dans le fichier `userdata1.yml`) :
|
||||
|
||||
```yaml
|
||||
---
|
||||
my_percent: 120
|
||||
```
|
||||
|
||||
```bash
|
||||
rougail -m structure6.yml -u yaml -yf userdata1.yml
|
||||
🔔 WARNINGS
|
||||
┗━━ the value "120" is an invalid integer for "my_percent", value must be less than "100", it will be
|
||||
ignored when loading from the YAML file "userdata1.yml"
|
||||
╭─────── Caption ────────╮
|
||||
│ Variable Default value │
|
||||
╰────────────────────────╯
|
||||
Variables:
|
||||
┗━━ 📓 my_percent: 10
|
||||
```
|
||||
|
||||
Ou pourra même généré une erreur :
|
||||
|
||||
```bash
|
||||
rougail -m structure6.yml -u yaml -yf userdata1.yml --cli.invalid_user_datas_error
|
||||
🛑 ERRORS
|
||||
┗━━ the value "120" is an invalid integer for "my_percent", value must be less than "100", it will be
|
||||
ignored when loading from the YAML file "userdata1.yml
|
||||
```
|
||||
|
||||
|
||||
#### Validation
|
||||
|
||||
Mais il est possible d'ajouter des validations complémentaires, par exemple ici vérifier que la variable est impaire (dans le fichier `structure_validators.yml`) :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_odd_variable:
|
||||
type: integer
|
||||
validators:
|
||||
- jinja: "{% if not (_.my_odd_variable % 2) %}not an odd integer{% endif %}"
|
||||
default:
|
||||
10
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
rougail -m structure7.yml
|
||||
🛑 ERRORS
|
||||
┗━━ "10" is an invalid integer for "my_odd_variable", not an odd integer
|
||||
```
|
||||
|
||||
Par contre cela passe avec une valeur impaire (dans le fichier `userdata_validators.yml`) :
|
||||
|
||||
```yaml
|
||||
---
|
||||
my_odd_variable: 11
|
||||
```
|
||||
|
||||
|
||||
```bash
|
||||
rougail -m structure_validators.yml -u yaml -yf userdata_validators.yml
|
||||
╭────────────── Caption ───────────────╮
|
||||
│ Variable Modified value │
|
||||
│ (⏳ Original default value) │
|
||||
╰──────────────────────────────────────╯
|
||||
Variables:
|
||||
┗━━ 📓 my_odd_variable: 11 ◀ loaded from the YAML file "userdata_validators.yml" (⏳ 10)
|
||||
```
|
||||
|
||||
### Cohérence globale
|
||||
|
||||
Une variable isolée peut être considéré comme étant de qualité mais devenir incohérence suivant le contexte.
|
||||
|
||||
Par exemple, si on demande une valeur minimum puis une valeur maximum, la minimal doit être inférieur à la maximal. Dans le fichier `structure_consistency.yml` on a :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_min_value: 11
|
||||
|
||||
my_max_value:
|
||||
default:
|
||||
variable: _.my_min_value
|
||||
validators:
|
||||
- jinja: |-
|
||||
{% if _.my_min_value >= _.my_max_value %}
|
||||
must but upper than {{ _.my_min_value }} (the value of "my_min_value")
|
||||
{% endif %}
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
rougail -m structure_consistency.yml
|
||||
🛑 ERRORS
|
||||
┗━━ "11" is an invalid integer for "my_max_value", must but upper than 11 (the value of "my_min_value")
|
||||
```
|
||||
|
||||
Si on défini la bonne valeur :
|
||||
|
||||
```yaml
|
||||
---
|
||||
my_max_value: 13
|
||||
```
|
||||
|
||||
```bash
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_min_value: 11
|
||||
|
||||
my_max_value:
|
||||
default:
|
||||
variable: _.my_min_value
|
||||
validators:
|
||||
- jinja: |-
|
||||
{% if _.my_min_value >= _.my_max_value %}
|
||||
must but upper than {{ _.my_min_value }} (the value of "my_min_value")
|
||||
{% endif %}
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
rougail -m structure_consistency.yml -u yaml -yf userdata_consistency.yml
|
||||
╭────────────── Caption ───────────────╮
|
||||
│ Variable Default value │
|
||||
│ Modified value │
|
||||
│ (⏳ Original default value) │
|
||||
╰──────────────────────────────────────╯
|
||||
Variables:
|
||||
┣━━ 📓 my_min_value: 11
|
||||
┗━━ 📓 my_max_value: 13 ◀ loaded from the YAML file "userdata_consistency.yml" (⏳ 11)
|
||||
```
|
||||
|
||||
|
||||
## Contrôle des accès
|
||||
|
||||
Le contrôle des accès est réalisé dès qu'on essaye d'accéder à une variable.
|
||||
|
||||
Dans Tiramisu il existe deux modes d'accès aux variables par défaut :
|
||||
|
||||
- le mode "lecture écriture" : ce mode est utilisé au moment du chargement des données utilisateurs
|
||||
- le mode "lecture seule" : ce mode est utilisé au moment de le représentation des valeurs
|
||||
|
||||
Il existe deux grands type de contrôle des accès :
|
||||
|
||||
- les modes
|
||||
- les propriétés
|
||||
|
||||
On pourrait rajouter les étiquettes à cette liste, même si l'objectif n'est pas spécialement de contrôler les accès via les étiquettes.
|
||||
|
||||
|
||||
### Les modes
|
||||
|
||||
On va partir sur un cas classic de définition des modes. On part sur trois mode :
|
||||
|
||||
- basic : de façon automatique les variables obligatoires sans valeur par défaut (dans ce cas l'acteur qui adapte la configuration devra obligatoirement renseigné des valeurs) et manuellement les variables que l'acteur qui définit les variables juge utile
|
||||
- standard : automatique les autres variables
|
||||
- advanced : les variables que l'acteur qui définit les variables décide de mettre
|
||||
|
||||
Par exemple créons deux variables dans `structure_mode.yml` avec des modes différents :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
standard_variable: default value
|
||||
|
||||
advanced_variable:
|
||||
mode: advanced
|
||||
default: default value
|
||||
...
|
||||
```
|
||||
|
||||
Avec le fichier `userdata_mode.yml` :
|
||||
|
||||
```yaml
|
||||
---
|
||||
standard_variable: value
|
||||
|
||||
advanced_variable: value
|
||||
```
|
||||
|
||||
```yaml
|
||||
rougail -m structure_mode.yml -u yaml -ff userdata_mode.yml --modes_level basic standard advanced
|
||||
╭────────────── Caption ───────────────╮
|
||||
│ Variable Modified value │
|
||||
│ (⏳ Original default value) │
|
||||
╰──────────────────────────────────────╯
|
||||
Variables:
|
||||
┣━━ 📓 standard_variable: value ◀ loaded from the YAML file "userdata_mode.yml" (⏳ default value)
|
||||
┗━━ 📓 advanced_variable: value ◀ loaded from the YAML file "userdata_mode.yml" (⏳ default value)
|
||||
```
|
||||
|
||||
Et interdisont aux acteurs adaptant la configuration de modifier les variables "advanced" :
|
||||
|
||||
```bash
|
||||
rougail -m structure_mode.yml -u yaml -yf userdata_mode.yml --modes_level basic standard advanced --cli.inaccessible_read_write_modes advanced
|
||||
🔔 WARNINGS
|
||||
┗━━ variable "advanced_variable" is advanced, it will be ignored when loading from the YAML file "userdata_mode.yml"
|
||||
╭───────────────────── Caption ─────────────────────╮
|
||||
│ Variable Default value │
|
||||
│ Undocumented variable Modified value │
|
||||
│ (⏳ Original default value) │
|
||||
╰───────────────────────────────────────────────────╯
|
||||
Variables:
|
||||
┣━━ 📓 standard_variable: value ◀ loaded from the YAML file "userdata_mode.yml" (⏳ default value)
|
||||
┗━━ 📓 advanced_variable: default value
|
||||
```
|
||||
|
||||
La gestion des modes est plutôt limités via la ligne de commande. Pour des besoins plus spécifique il sera nécessaire de passer par la bibliothèque.
|
||||
|
||||
|
||||
### Les propriétés
|
||||
|
||||
Les deux propriétés importantes sont :
|
||||
|
||||
- les variables cachée
|
||||
- les variables désactivée
|
||||
|
||||
Ces propriétés peuvent être fixe ou calculer suivant le contexte
|
||||
|
||||
|
||||
#### Variable cachée
|
||||
|
||||
Une variable cachée est une variable qui ne sera pas modifiable par l'utilisateur et que ne sera pas présente en mode "lecture écriture" mais présente en mode "lecture seule".
|
||||
|
||||
Généralement on utilise ce type de variable lorsqu'on ne veut pas que l'acteur qui adapte la configuration puisse modifier cette variable ou alors parce que la valeur de la variable est issu d'un calcul.
|
||||
|
||||
|
||||
#### Variable désactivée
|
||||
|
||||
Une variable désactiver est une variable qui sera accessible a aucun des acteurs.
|
||||
|
||||
On utilise généralement cette propriété de façon dynamique pour supprimer l'accès à cette variable suivant le context.
|
||||
|
||||
Prenons un exemple de variable cachée (donc non modifiable) et ajoutons un variable "use_proxy" qui permet de définir une adresse proxy si on le souhait. Pour cela nous allons créer le fichier suivante : `structure_properties.yml` :
|
||||
|
||||
```yaml
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
hidden_variable:
|
||||
hidden: true
|
||||
default: accessible
|
||||
|
||||
use_proxy: false
|
||||
|
||||
proxy_address:
|
||||
disabled:
|
||||
variable: use_proxy
|
||||
when: false
|
||||
...
|
||||
```
|
||||
|
||||
```bash
|
||||
rougail -m structure_properties.yml
|
||||
╭────────────── Caption ──────────────╮
|
||||
│ Variable Default value │
|
||||
│ Unmodifiable variable │
|
||||
╰─────────────────────────────────────╯
|
||||
Variables:
|
||||
┣━━ 📓 hidden_variable: accessible
|
||||
┗━━ 📓 use_proxy: false
|
||||
```
|
||||
|
||||
Essayons de modifier la variable cachée et de configurer le proxy dans le fichier `userdata_properties.yml` :
|
||||
|
||||
```yaml
|
||||
---
|
||||
hidden_variable: inaccessible
|
||||
|
||||
use_proxy: true
|
||||
|
||||
proxy_address: https://proxy.foo.net:4128/
|
||||
```
|
||||
|
||||
```yaml
|
||||
rougail -m structure_properties.yml -u yaml -ff userdata_properties.yml
|
||||
🔔 WARNINGS
|
||||
┗━━ variable "hidden_variable" is hidden, it will be ignored when loading from the YAML file "userdata_properties.yml"
|
||||
╭───────────────────── Caption ─────────────────────╮
|
||||
│ Variable Default value │
|
||||
│ Unmodifiable variable Modified value │
|
||||
│ (⏳ Original default value) │
|
||||
╰───────────────────────────────────────────────────╯
|
||||
Variables:
|
||||
┣━━ 📓 hidden_variable: accessible
|
||||
┣━━ 📓 use_proxy: true ◀ loaded from the YAML file "userdata_properties.yml" (⏳ false)
|
||||
┗━━ 📓 proxy_address: https://proxy.foo.net:4128/ ◀ loaded from the YAML file "userdata_properties.yml"
|
||||
```
|
||||
|
||||
|-------------|------------------------------------------|----------------------------|----------|----------|--------------------|----------------------|
|
||||
| Étape | Acteur | Cycle de vies | Variable | Valeur | Lecture | Écriture | Propriétés |
|
||||
|-------------|------------------------------------------|----------------------------|----------|----------|--------------------|----------------------|
|
||||
| Structurals | défini les variables | déclaration + présentation | mutable | défaut | | |
|
||||
| UserDatas | adapte les valeurs | affectration | immuable | mutable | lecture + écriture | hidden + disabled |
|
||||
| Outputs | utilise les variables avec leurs valeurs | lecture | immuable | immuable | lecture | disabled + mandatory |
|
||||
| | | destruction | | | | |
|
||||
|-------------|------------------------------------------|----------------------------|----------|----------|--------------------|----------------------|
|
||||
2
docs/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.venv/
|
||||
build/
|
||||
20
docs/Makefile
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
73
docs/_static/terminal.css
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
.terminal {
|
||||
background-color: #000000; /* Fond noir */
|
||||
color: #00ff00; /* Texte vert, typique des anciens terminaux */
|
||||
font-family: 'Courier New', Courier, monospace; /* Police à chasse fixe */
|
||||
padding: 5px; /* Espace réduit autour du texte */
|
||||
border-radius: 5px;
|
||||
border: 1px solid #00ff00; /* Bordure verte */
|
||||
white-space: pre-wrap;
|
||||
overflow-x: auto; /* Défilement horizontal si nécessaire */
|
||||
box-shadow: 0 0 10px rgba(0, 255, 0, 0.5); /* Ombre verte pour un effet rétro */
|
||||
display: flex; /* Active Flexbox */
|
||||
line-height: 1.2; /* Espacement entre les lignes */
|
||||
display: inline-block; /* Pour que le fond s'adapte au contenu */
|
||||
width: 100%; /* Largeur maximale */
|
||||
}
|
||||
|
||||
.terminal,
|
||||
.terminal * {
|
||||
border: none !important; /* Supprime toutes les bordures à l'intérieur du terminal */
|
||||
}
|
||||
|
||||
.terminal .highlight {
|
||||
margin: 0; /* Supprime les marges */
|
||||
padding: 0; /* Supprime les paddings */
|
||||
background-color: transparent; /* Fond transparent pour éviter les conflits */
|
||||
}
|
||||
|
||||
.terminal .highlight pre {
|
||||
margin: 0; /* Supprime les marges */
|
||||
padding: 0; /* Supprime les paddings */
|
||||
background-color: transparent; /* Fond transparent */
|
||||
color: inherit; /* Hérite la couleur du texte du parent */
|
||||
font-family: inherit; /* Hérite la police du parent */
|
||||
line-height: inherit; /* Hérite l'espacement des lignes du parent */
|
||||
}
|
||||
|
||||
.terminal .highlight pre span {
|
||||
display: inline; /* Évite les espaces inutiles causés par inline-block */
|
||||
margin: 0; /* Supprime les marges */
|
||||
padding: 0; /* Supprime les paddings */
|
||||
}
|
||||
|
||||
.terminal .go {
|
||||
color: #00ff00; /* Couleur pour le texte de sortie du terminal */
|
||||
}
|
||||
|
||||
/* raw html output css */
|
||||
|
||||
/* Styles communs pour les deux classes */
|
||||
.error-box, .output {
|
||||
padding: 10px; /* Espace intérieur */
|
||||
font-size: 0.8em; /* Taille de police plus petite */
|
||||
width: fit-content; /* Ajuste la largeur au contenu */
|
||||
border-radius: 5px; /* Coins arrondis */
|
||||
display: inline-block; /* Pour que la boîte s'ajuste au contenu */
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.error-box pre, .output pre {
|
||||
margin: 0; /* Supprime la marge par défaut du <pre> */
|
||||
}
|
||||
|
||||
/* Styles spécifiques à la classe error-box */
|
||||
.error-box {
|
||||
border: 2px solid #ff0000; /* Bordure rouge */
|
||||
background-color: #ffe6e6; /* Fond légèrement rouge */
|
||||
}
|
||||
|
||||
/* Styles spécifiques à la classe output */
|
||||
.output {
|
||||
border: 2px solid #00ff00; /* Bordure verte */
|
||||
background-color: #e6ffe6; /* Fond légèrement vert */
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
variable
|
||||
<https://en.wikipedia.org/wiki/Variable_(computer_science)>`_
|
||||
|
|
@ -117,7 +117,7 @@ Here is a simple example of validating values:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_variable:
|
||||
validators:
|
||||
- type: jinja
|
||||
|
|
@ -142,7 +142,7 @@ In the constraint, it is possible to specify the error level and put it as a war
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_variable:
|
||||
validators:
|
||||
- type: jinja
|
||||
|
|
@ -161,7 +161,7 @@ Verification with parameters
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_hidden_variable:
|
||||
disabled: true
|
||||
my_variable:
|
||||
|
|
@ -189,7 +189,7 @@ An example with a suffix type parameter:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
varname:
|
||||
multi: true
|
||||
default:
|
||||
|
|
@ -221,7 +221,7 @@ An example with an index type parameter:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
family:
|
||||
type: leadership
|
||||
leader:
|
||||
|
|
@ -230,7 +230,7 @@ An example with an index type parameter:
|
|||
- val1
|
||||
- val2
|
||||
follower1:
|
||||
type: number
|
||||
type: integer
|
||||
validators:
|
||||
- type: jinja
|
||||
jinja: |
|
||||
|
|
@ -249,7 +249,7 @@ In a first dictionary, let's declare our variable and its verification function:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_variable:
|
||||
validators:
|
||||
- type: jinja
|
||||
|
|
@ -263,7 +263,7 @@ In a second dictionary it is possible to redefine the calculation:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_variable:
|
||||
redefine: true
|
||||
validators:
|
||||
|
|
@ -280,7 +280,7 @@ Here is a third dictionary in which we remove the validation:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_variable:
|
||||
redefine: true
|
||||
validators:
|
||||
|
|
|
|||
29
docs/cli.rst
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
:orphan:
|
||||
|
||||
::
|
||||
|
||||
rougail --cli.versions
|
||||
|
||||
tiramisu: 5.2.0a9
|
||||
tiramisu-cmdline-parser: 0.7.0a1
|
||||
rougail: 1.2.0a29
|
||||
rougail-cli: 0.2.0a19
|
||||
rougail-user-data-environment: 0.1.0a9
|
||||
rougail-user-data-yaml: 0.2.0a11
|
||||
rougail-output-console: 0.2.0a11
|
||||
rougail-output-json: 0.2.0a8
|
||||
|
||||
::
|
||||
|
||||
env ROUGAIL_MANUAL.USE_FOR_HTTPS=true rougail -m structfile/proxy2.yml -u yaml environment --yaml.filename userdata/proxy.yml -o json
|
||||
|
||||
::
|
||||
|
||||
env ROUGAIL_MANUAL.USE_FOR_HTTPS=true rougail -m structfile/proxy2.yml -u yaml --yaml.filename userdata/proxy.yml -o json --json.get manual.https_proxy --json.read_write
|
||||
{
|
||||
"address": "toto.fr",
|
||||
"port": "8888"
|
||||
}
|
||||
|
||||
|
||||
|
||||
44
docs/concepts.rst
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
Abstract presentation
|
||||
=========================
|
||||
|
||||
Why another validating library?
|
||||
-------------------------------------
|
||||
|
||||
Using Rougail in your application or your python libraries can tansform end user consumer defined consistency rules into highly consistent business objects.
|
||||
|
||||
We then have to say that the handling system used to ensure the variables integrity is another python library, called :term:`Tiramisu`. Rougail is currently strongly affiliated with Tiramisu.
|
||||
|
||||
.. note:: Rougail is currently intended to work in coordination with :term:`Tiramisu` and **is not** intended to be connected with any other consistency handling system.
|
||||
|
||||
Explained differently, Rougail allows you to easily implement an integration of the powerful tiramisu consistency handling system.
|
||||
|
||||
What is a consistency handling system ?
|
||||
-------------------------------------------
|
||||
|
||||
.. questions:: Rougail, Tiramisu: What is it all about?
|
||||
|
||||
**Question**: OK, I have understood that the Rougail library allows me to take advantage of the :xref:`tiramisu` consistency handling library. But what is all this for? What is exactly a consistency handling system? And again, what is this :xref:`Tiramisu library <tiramisu library>` used for?
|
||||
|
||||
**Answer**: Well, we will explain in details what this :xref:`tiramisu` library is and what Rougail is.
|
||||
|
||||
In (very) short:
|
||||
|
||||
- Rougail is the YAML consistency description of a :term:`context`\ 's situation
|
||||
- Tiramisu is the consistency engine linter
|
||||
|
||||
.. glossary::
|
||||
|
||||
Tiramisu
|
||||
|
||||
:xref:`tiramisu` is a consistency handling system that has initially been designed
|
||||
in the configuration management scope. Until now,
|
||||
this library is generally used to handle configuration options.
|
||||
|
||||
It manages variables and group of variables. In the Tiramisu scope we call
|
||||
it *options* and *option descriptions*.
|
||||
|
||||
Here is the :xref:`tiramisu documentation <tiramisu>`.
|
||||
|
||||
In the Rougail scope, we call it :term:`variables <variable>` and :term:`families <family>`.
|
||||
In Rougail, the families and variables are located in the :term:`structure files <structure file>`.
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ It is possible to write the condition in Jinja:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
condition:
|
||||
default: 'do not hide!'
|
||||
my_variable:
|
||||
|
|
@ -213,7 +213,7 @@ A variable can therefore be calculated via the result of another variable. Pleas
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
condition:
|
||||
type: boolean
|
||||
my_variable:
|
||||
|
|
@ -233,7 +233,7 @@ To delete the calculation from a variable, simply do in a new dictionary:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_variable:
|
||||
redefine: true
|
||||
hidden:
|
||||
|
|
|
|||
132
docs/conf.py
|
|
@ -1,24 +1,20 @@
|
|||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file does only contain a selection of the most common options. For a
|
||||
# full list see the documentation:
|
||||
# http://www.sphinx-doc.org/en/master/config
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
import sys, os
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
#sys.path.append(os.path.abspath('ext'))
|
||||
sys.path.append('.')
|
||||
|
||||
#---- debug mode ----
|
||||
# shows/hides the todos
|
||||
todo_include_todos = True
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'Rougail'
|
||||
copyright = '2019-2023, Silique'
|
||||
copyright = '2019-2025, Silique'
|
||||
author = 'gwen'
|
||||
|
||||
# The short X.Y version
|
||||
|
|
@ -33,55 +29,64 @@ release = '1.0'
|
|||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.extlinks', 'sphinx_lesson',
|
||||
#'myst_parser', 'sphinx.ext.extlinks'
|
||||
'sphinx.ext.extlinks', 'sphinx_lesson', 'sphinx.ext.todo',
|
||||
'ext.xref', 'ext.extinclude'
|
||||
]
|
||||
#
|
||||
#myst_enable_extensions = [
|
||||
# "amsmath",
|
||||
# "attrs_inline",
|
||||
# "colon_fence",
|
||||
# "deflist",
|
||||
# "dollarmath",
|
||||
# "fieldlist",
|
||||
# "html_admonition",
|
||||
# "html_image",
|
||||
## "linkify",
|
||||
# "replacements",
|
||||
# "smartquotes",
|
||||
# "strikethrough",
|
||||
# "substitution",
|
||||
# "tasklist",
|
||||
#]
|
||||
|
||||
#---- disable highlight warnings with yaml new version ----
|
||||
# Configuration pour les blocs de code
|
||||
highlight_language = 'yaml'
|
||||
|
||||
# Options spécifiques pour YAML
|
||||
highlight_options = {
|
||||
'yaml': {
|
||||
'startinline': True
|
||||
}
|
||||
}
|
||||
|
||||
suppress_warnings = [
|
||||
'misc.highlighting_failure'
|
||||
]
|
||||
|
||||
#---- xref links ----
|
||||
#import the xref.py extension
|
||||
xref_links = {"link_name" : ("user text", "url")}
|
||||
|
||||
#link_name = "Sphinx External Links"
|
||||
#user_text = "modified External Links Extension"
|
||||
#url = "http://www.sphinx-doc.org/en/stable/ext/extlinks.html"
|
||||
#enables syntax like:
|
||||
" :xref:`tiramisu` "
|
||||
links = {
|
||||
'tiramisu': ('Tiramisu', 'https://tiramisu.readthedocs.io/en/latest/'),
|
||||
'tiramisu library': ('Tiramisu library homepage', 'https://forge.cloud.silique.fr/stove/tiramisu'),
|
||||
}
|
||||
xref_links.update(links)
|
||||
|
||||
#---- ext links ----
|
||||
# **extlinks** 'sphinx.ext.extlinks',
|
||||
# enables syntax like :proxy:`my source <hello>` in the src files
|
||||
extlinks = {'proxy': ('/proxy/%s.html',
|
||||
'external link: ')}
|
||||
# enables syntax like
|
||||
" :source:`v1.1_010/firefox/00-proxy.yml` "
|
||||
extlinks = {'source': ('https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/%s',
|
||||
'source: %s'),
|
||||
'tiramisu': ('https://tiramisu.readthedocs.io/en/latest/%s', 'tiramisu: %s'),
|
||||
'tutorial': ('https://forge.cloud.silique.fr/stove/rougail-tutorials/%s', 'tutorial %s'),
|
||||
}
|
||||
|
||||
#---- options for HTML output ----
|
||||
default_role = "code"
|
||||
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
html_short_title = "Rougail"
|
||||
html_title = "Rougail documenation"
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
html_show_sourcelink = False
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
html_show_sphinx = False
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
html_show_copyright = True
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = None
|
||||
html_static_path = ['_static']
|
||||
html_css_files = ['terminal.css']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
|
@ -109,41 +114,8 @@ language = 'en'
|
|||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path.
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
exclude_patterns = ['.venv', 'build', '_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = None
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
#html_theme = 'alabaster'
|
||||
# **themes**
|
||||
#html_theme = 'bizstyle'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Custom sidebar templates, must be a dictionary that maps document names
|
||||
# to template names.
|
||||
#
|
||||
# The default sidebars (for documents that don't match any pattern) are
|
||||
# defined by theme itself. Builtin themes are using these templates by
|
||||
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||
# 'searchbox.html']``.
|
||||
#
|
||||
# html_sidebars = {}
|
||||
|
||||
def setup(app):
|
||||
app.add_css_file('css/custom.css')
|
||||
|
|
|
|||
|
|
@ -14,3 +14,47 @@ This process describes how to install and run the project locally, e.g. for deve
|
|||
*Nota*: command is to be executed through the terminal
|
||||
|
||||
`pip install rougail`
|
||||
|
||||
Code quality
|
||||
---------------
|
||||
|
||||
We are using `pre-commit <https://pre-commit.com/>`_, there is a :file:`.pre-commit-config.yaml`
|
||||
pre-commit config file in the root's project.
|
||||
|
||||
You need to:
|
||||
|
||||
- install the pre-commit library::
|
||||
|
||||
pip install pre-commit
|
||||
|
||||
- registrer the pre-commit git hooks with this command::
|
||||
|
||||
pre-commit install
|
||||
|
||||
- launch the quality code procedure with::
|
||||
|
||||
pre-commit
|
||||
|
||||
or simply just commit your changes, pre-commit will automatically be launched.
|
||||
|
||||
.. attention:: If an error is found, the commit will not happen.
|
||||
You must resolve all errors that pre-commit that pre-commit points out to you before.
|
||||
|
||||
.. note:: If you need for some reason to disable `pre-commit`, just set
|
||||
the `PRE_COMMIT_ALLOW_NO_CONFIG` environment variable before commiting::
|
||||
|
||||
PRE_COMMIT_ALLOW_NO_CONFIG=1 git commit
|
||||
|
||||
Coding standard
|
||||
------------------
|
||||
|
||||
We use black
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
And some YAML and JSON validators.
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
Dictionary conventions
|
||||
=========================
|
||||
|
||||
Dictionary file naming convention
|
||||
------------------------------------
|
||||
|
||||
The order of dictionaries is important for the order in which variables and families are created.
|
||||
|
||||
The files must therefore be started with two numbers followed by a hyphen.
|
||||
|
||||
For example: `00-base.xml`
|
||||
|
||||
Naming convention for families and variables
|
||||
-----------------------------------------------
|
||||
|
||||
The only restriction on the name of families and variables is that the name must not start with the `"_"` (undescore) character.
|
||||
|
||||
However, it is preferable to only use lowercase ASCII letters, numbers and the `"_"` (undescore) character.
|
||||
The snake case typographic convention is therefore used.
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
The dictionaries
|
||||
=====================
|
||||
|
||||
What do you mean by :term:`dictionary`?
|
||||
-------------------------------------------
|
||||
|
||||
A :term:`dictionary` is a YAML file whose structure is described in this documentation page.
|
||||
|
||||
A dictionary contains a set of variables loaded into :term:`Tiramisu`, usable at any time, especially in a :term:`templates`.
|
||||
|
||||
:term:`Families` and :term:`variables` can be defined in several dictionaries. These dictionaries are then aggregated.
|
||||
|
||||
Dictionaries are loaded in the directory order defined by the `dictionaries_dir` configuration parameter.
|
||||
Each directory is loaded one after the other.
|
||||
Inside these directories the YAML files will be classified in alphabetical order.
|
||||
|
||||
There is no alphabetical ordering of all YAML files in all directories.
|
||||
|
||||
It is also possible to :term:`redefine` elements to change the behavior of a family or a variable.
|
||||
|
||||
The default namespace
|
||||
-------------------------
|
||||
|
||||
The families and variables contained in these dictionaries are ordered, by default, in the `rougail` namespace. It is possible to change the name of this namespace :doc:`with the `variable_namespace` parameter of the configuration <configuration>`.
|
||||
|
||||
This namespace is a bit special, it can access variables in another namespace.
|
||||
|
||||
The extra dictionaries
|
||||
---------------------------
|
||||
|
||||
An extra is a different namespace. The idea is to be able to classify the variables by theme.
|
||||
|
||||
Extra namespaces must be declared :doc:`when configuring Rougail <configuration>`.
|
||||
|
||||
In this namespace we cannot access variables from another `extra` namespace.
|
||||
On the other hand, it is possible to access the variable of the default namespace.
|
||||
42
docs/documentation.rst
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
Documentation standards
|
||||
==========================
|
||||
|
||||
|
||||
YAML bloc code
|
||||
-------------------
|
||||
|
||||
If you have some YAML
|
||||
The rougail YAML follows the YAML 1.2 conventions,
|
||||
you might encounter a warning like this one::
|
||||
|
||||
WARNING: Le lexème du bloc_littéral ' %YAML 1.2\n ---\n version: 1.1\n\n ...'
|
||||
en tant que "yaml" a entraîné une erreur au niveau du jeton : '%'.
|
||||
Réessayer en mode relaxé.
|
||||
|
||||
.. code-block:: rst
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`dist/00-base.yml` file content
|
||||
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable: my_value_extra # a simple variable
|
||||
...
|
||||
|
||||
Because the sphinx-doc tool is not YAML 1.2 ready yet.
|
||||
The solution is simple, just escape the `%` like this:
|
||||
|
||||
.. code-block:: rst
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`dist/00-base.yml` file content
|
||||
|
||||
\%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable: my_value_extra # a simple variable
|
||||
...
|
||||
|
||||
95
docs/ext/extinclude.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from docutils import nodes
|
||||
from sphinx.application import Sphinx
|
||||
from sphinx.util.docutils import SphinxDirective, SphinxRole
|
||||
from sphinx.util.typing import ExtensionMetadata
|
||||
from sphinx.directives.code import LiteralInclude, container_wrapper
|
||||
|
||||
import requests
|
||||
from requests.exceptions import RequestException
|
||||
from docutils.parsers.rst import directives
|
||||
|
||||
class ExtInclude(LiteralInclude):
|
||||
"""A directive to include code that comes from an url
|
||||
|
||||
Sample use::
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/firefox/00-proxy.yml
|
||||
:linenos:
|
||||
:language: yaml
|
||||
:caption: this is a interesting code
|
||||
|
||||
- parameter required
|
||||
- linenos, language and caption are optionnal.
|
||||
|
||||
:default language: yaml
|
||||
:default caption: extinclude parameter (url)
|
||||
|
||||
"""
|
||||
|
||||
def run(self) -> list[nodes.Node]:
|
||||
url = self.arguments[0]
|
||||
|
||||
try:
|
||||
headers = {
|
||||
'accept': 'application/text',
|
||||
'Content-Type': 'application/text',
|
||||
}
|
||||
response = requests.get(url, headers=headers)
|
||||
response.raise_for_status() # This will raise an exception for 4xx/5xx status codes
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if response.status_code == 404:
|
||||
error_msg = f"extinclude: URL not found (404): {url}"
|
||||
else:
|
||||
error_msg = f"extinclude: HTTP error {response.status_code}: {url}"
|
||||
|
||||
# Create an error node that will be displayed in the documentation
|
||||
error_node = nodes.error()
|
||||
para = nodes.paragraph()
|
||||
para += nodes.Text(error_msg)
|
||||
error_node += para
|
||||
self.state.document.reporter.warning(error_msg, line=self.lineno)
|
||||
return [error_node]
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
error_msg = f"extinclude: Failed to fetch URL {url}: {str(e)}"
|
||||
|
||||
# Create an error node that will be displayed in the documentation
|
||||
error_node = nodes.error()
|
||||
para = nodes.paragraph()
|
||||
para += nodes.Text(error_msg)
|
||||
error_node += para
|
||||
self.state.document.reporter.warning(error_msg, line=self.lineno)
|
||||
return [error_node]
|
||||
|
||||
code = response.text
|
||||
|
||||
literal = nodes.literal_block(code, code)
|
||||
if 'language' in self.options:
|
||||
literal['language'] = self.options['language']
|
||||
else:
|
||||
literal['language'] = 'yaml'
|
||||
literal['linenos'] = 'linenos' in self.options
|
||||
if 'caption' in self.options:
|
||||
caption = self.options.get('caption')
|
||||
else:
|
||||
caption = url
|
||||
literal['caption'] = caption
|
||||
if 'name' in self.options:
|
||||
literal['name'] = self.options.get('name')
|
||||
literal = container_wrapper(self, literal, caption)
|
||||
self.add_name(literal)
|
||||
|
||||
return [literal]
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> ExtensionMetadata:
|
||||
app.add_directive('extinclude', ExtInclude)
|
||||
|
||||
return {
|
||||
'version': '0.1',
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
67
docs/ext/xref.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
"""adds link url in the global scope
|
||||
|
||||
sample use:
|
||||
|
||||
:xref:`Tiramisu <tiramisu>`
|
||||
|
||||
You must declare in the `conf.py`
|
||||
|
||||
::
|
||||
|
||||
#---- xref links ----
|
||||
#import the xref.py extension
|
||||
xref_links = {"link_name" : ("user text", "url")}
|
||||
#link_name = "Sphinx External Links"
|
||||
#user_text = "modified External Links Extension"
|
||||
#url = "http://www.sphinx-doc.org/en/stable/ext/extlinks.html"
|
||||
#enables syntax like:
|
||||
" :xref:`tiramisu` "
|
||||
links = {
|
||||
'tiramisu': ('Tiramisu', 'https://tiramisu.readthedocs.io/en/latest/'),
|
||||
'tiramisu library': ('Tiramisu library homepage', 'https://forge.cloud.silique.fr/stove/tiramisu'),
|
||||
}
|
||||
xref_links.update(links)
|
||||
|
||||
|
||||
|
||||
"""
|
||||
from docutils import nodes
|
||||
|
||||
from sphinx.util import caption_ref_re
|
||||
|
||||
def xref( typ, rawtext, text, lineno, inliner, options={}, content=[] ):
|
||||
|
||||
title = target = text
|
||||
titleistarget = True
|
||||
# look if explicit title and target are given with `foo <bar>` syntax
|
||||
brace = text.find('<')
|
||||
if brace != -1:
|
||||
titleistarget = False
|
||||
m = caption_ref_re.match(text)
|
||||
if m:
|
||||
target = m.group(2)
|
||||
title = m.group(1)
|
||||
else:
|
||||
# fallback: everything after '<' is the target
|
||||
target = text[brace+1:]
|
||||
title = text[:brace]
|
||||
|
||||
link = xref.links[target]
|
||||
|
||||
if brace != -1:
|
||||
pnode = nodes.reference(target, title, refuri=link[1])
|
||||
else:
|
||||
pnode = nodes.reference(target, link[0], refuri=link[1])
|
||||
|
||||
return [pnode], []
|
||||
|
||||
def get_refs(app):
|
||||
|
||||
xref.links = app.config.xref_links
|
||||
|
||||
def setup(app):
|
||||
|
||||
app.add_config_value('xref_links', {}, True)
|
||||
app.add_role('xref', xref)
|
||||
app.connect("builder-inited", get_refs)
|
||||
|
||||
143
docs/family.rst
|
|
@ -1,49 +1,74 @@
|
|||
A family
|
||||
============
|
||||
The families
|
||||
=============
|
||||
|
||||
Synopsis
|
||||
---------
|
||||
|
||||
A family is a container of variables and subfamily.
|
||||
.. glossary::
|
||||
|
||||
family
|
||||
|
||||
A family of variables is simply a collection of variables that refer to
|
||||
the same business model category. It's just a variables container.
|
||||
Think of it as a container as well as a namespace.
|
||||
|
||||
.. attention:: A family without a subfamily or subvariable will be automatically deleted.
|
||||
|
||||
Name
|
||||
-------------
|
||||
Naming conventions
|
||||
------------------------
|
||||
|
||||
It is with this name that we will be able to interact with the family.
|
||||
It is with its name that we will be able to interact with the family.
|
||||
|
||||
It's best to follow the :ref:`convention on variable names`.
|
||||
.. seealso::
|
||||
|
||||
Have a look at the :ref:`convention on variable names`.
|
||||
|
||||
Shorthand declaration
|
||||
----------------------------
|
||||
|
||||
Shorthand declaration is a way to declare a family in a single line. But you can only define family name and description.
|
||||
|
||||
To create a family, just add a key with it's name and variables as values. Attention, do not declare any other attributs.
|
||||
|
||||
By default, the description of the variable is the family name.
|
||||
If you add comment in same line of name, this comment is use as description:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.1'
|
||||
my_family: # This is a great family
|
||||
variable:
|
||||
|
||||
Parameters
|
||||
---------------
|
||||
|
||||
.. FIXME: faire une page sur la "convention on variable names"
|
||||
.. todo:: faire une page sur la "convention on variable names"
|
||||
|
||||
.. list-table::
|
||||
.. list-table::
|
||||
:widths: 15 45
|
||||
:header-rows: 1
|
||||
|
||||
* - Parameter
|
||||
- Comments
|
||||
|
||||
|
||||
* - type, _type
|
||||
|
||||
`string`
|
||||
|
||||
|
||||
- possile values:
|
||||
|
||||
|
||||
- `family` (**default value**)
|
||||
- `leadership`
|
||||
- `dynamic`
|
||||
|
||||
|
||||
.. note:: If a subfamily or a subvariable already has the name `"type"`, it is possible to use the `"_type"` attribute.
|
||||
|
||||
* - description, _description
|
||||
|
||||
`string`
|
||||
- Description of the family.
|
||||
|
||||
|
||||
User information to understand the usefulness of the family.
|
||||
|
||||
..note:: If a subfamily or subvariable already has the name "description" it is possible to use the "_description" attribute.
|
||||
|
|
@ -53,7 +78,7 @@ Parameters
|
|||
`string`
|
||||
- Additional help associated with the family.
|
||||
|
||||
.. note:: If a subfamily or a subvariable already has the name "help" it is possible to use the "_help" attribute.
|
||||
.. note:: If a subfamily or a subvariable already has the name "help" it is possible to use the "_help" attribute.
|
||||
|
||||
* - mode, _mode
|
||||
|
||||
|
|
@ -61,30 +86,30 @@ Parameters
|
|||
- Family mode.
|
||||
|
||||
The default mode of a family is the smallest mode of the parent families, child variables, or child families that are contained in that family.
|
||||
|
||||
|
||||
This mode also allows you to define the default mode for variables or families included in this family.
|
||||
|
||||
|
||||
.. note:: If a subfamily or a subvariable already has the name "mode" it is possible to add the "_mode" attribute.
|
||||
|
||||
* - hidden, _hidden
|
||||
|
||||
`string`
|
||||
- Invisible family.
|
||||
|
||||
|
||||
Allows you to hide a family as well as the variables or families included in this family.
|
||||
|
||||
This means that the family will no longer be visible in `read-write` mode, but only for calculations or in `read-only` mode.
|
||||
|
||||
.. note:: If a subfamily or a subvariable already has the name "hidden" it is possible to add the "_hidden" attribute.
|
||||
.. note:: If a subfamily or a subvariable already has the name "hidden" it is possible to add the "_hidden" attribute.
|
||||
|
||||
* - disabled, _disabled
|
||||
|
||||
`string`
|
||||
|
||||
- Disabled family.
|
||||
|
||||
|
||||
Allows you to deactivate a family as well as the variables or families included in this family.
|
||||
|
||||
|
||||
This means that the family will no longer be visible to the user but also to a :term:`calculation`.
|
||||
|
||||
.. note:: If a subfamily or a subvariable already has the name "disabled" it is possible to use the "_disabled" attribute.
|
||||
|
|
@ -92,25 +117,18 @@ Parameters
|
|||
Dynamically created family
|
||||
-----------------------------
|
||||
|
||||
To create a family dynamically, you must create a fictitious family linked to a variable.
|
||||
The family name and description will actually be the prefix of the new name / description.
|
||||
The suffix will come from the value of the bound variable.
|
||||
The name of the families and variables it contains will be preserved, however, the description will be the prefix of the real description.
|
||||
To create a family dynamically, you must create a fictitious family linked to a calculation.
|
||||
The family name will actually be the prefix of the new name. Alternativly you can specify the suffix in the name, ie `my_{{ suffix }}_name`.
|
||||
The suffix will come from the calculation.
|
||||
|
||||
Obviously if the content of the linked variable were to evolve, new dynamic families will appear or disappear.
|
||||
Obviously if the result of calculation were to evolve, new dynamic families will appear or disappear.
|
||||
|
||||
To note that:
|
||||
|
||||
- the variable linked to the family must be a multiple variable
|
||||
- it is not possible to put a simple family into a dynamic family
|
||||
- it is possible to put a leading family into a dynamic family
|
||||
|
||||
Leader or foller variable
|
||||
Leader or follower variable
|
||||
-----------------------------
|
||||
|
||||
A leader family has a typical attribute of “leadership”. The type is required.
|
||||
|
||||
A leader family
|
||||
A leader family
|
||||
----------------
|
||||
|
||||
The leader and follower variables are placed in a leader family.
|
||||
|
|
@ -150,25 +168,25 @@ If a leader variable is hidden or disabled, the follower variables will be hidde
|
|||
Examples
|
||||
----------
|
||||
|
||||
Simple family
|
||||
Simple family:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_family:
|
||||
type: family
|
||||
description: This is a great family
|
||||
help: This is the help of a great family
|
||||
mode: expert
|
||||
|
||||
Dynamically created family
|
||||
Dynamically created family
|
||||
----------------------------
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
varname:
|
||||
multi: true
|
||||
default:
|
||||
|
|
@ -176,20 +194,50 @@ Dynamically created family
|
|||
- val2
|
||||
my_dyn_family_:
|
||||
type: dynamic
|
||||
variable: rougail.varname
|
||||
description: 'Describe '
|
||||
dynamic:
|
||||
type: variable
|
||||
variable: rougail.varname
|
||||
description: 'Describe'
|
||||
my_dyn_var:
|
||||
type: string
|
||||
description: 'Variable description for '
|
||||
description: 'Variable description'
|
||||
|
||||
This will dynamically create two families:
|
||||
|
||||
- "rougail.my_dyn_family_val1" with the description "Describe val1"
|
||||
- "rougail.my_dyn_family_val2" with the description "Describe val2"
|
||||
- "rougail.my_dyn_family_val1"
|
||||
- "rougail.my_dyn_family_val2"
|
||||
|
||||
In the dynamic family "rougail.my_dyn_family_val1" we will find a variable "my_dyn_var" with the description "Variable description for val1".
|
||||
In the dynamic family "rougail.my_dyn_family_val1" we will find a variable "my_dyn_var".
|
||||
|
||||
Leader or follower variable
|
||||
Here is a second example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.1'
|
||||
varname:
|
||||
multi: true
|
||||
default:
|
||||
- val1
|
||||
- val2
|
||||
my_dyn_{{ suffix }}_family:
|
||||
type: dynamic
|
||||
dynamic:
|
||||
type: variable
|
||||
variable: rougail.varname
|
||||
description: 'Describe'
|
||||
my_dyn_var:
|
||||
type: string
|
||||
description: 'Variable description'
|
||||
|
||||
This will dynamically create two families:
|
||||
|
||||
- "rougail.my_dyn_val1_family"
|
||||
- "rougail.my_dyn_val2_family"
|
||||
|
||||
In the dynamic family "rougail.my_dyn_val1_family" we will find a variable "my_dyn_var".
|
||||
|
||||
Leader or follower variable
|
||||
-------------------------------
|
||||
|
||||
Definition of leader and follower variables
|
||||
|
|
@ -200,7 +248,7 @@ Here is an example of defining a leading variable and two following variables:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
family:
|
||||
type: leadership
|
||||
leader:
|
||||
|
|
@ -217,7 +265,6 @@ To add a new follower variable, in a new dictionary, simply define one or more n
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
family:
|
||||
follower3:
|
||||
|
||||
|
|
|
|||
281
docs/fill.rst
|
|
@ -23,64 +23,64 @@ Parameters
|
|||
|
||||
Depending on the types of calculation, the parameters will be different:
|
||||
|
||||
.. list-table::
|
||||
.. list-table::
|
||||
:widths: 15 25 20 15
|
||||
:header-rows: 1
|
||||
|
||||
* - Calculation type
|
||||
|
||||
* - Calculation type
|
||||
- Parameter
|
||||
- Comments
|
||||
- Sample
|
||||
|
||||
* -
|
||||
- **type**
|
||||
|
||||
* -
|
||||
- **type**
|
||||
|
||||
`string`
|
||||
|
||||
|
||||
`mandatory`
|
||||
|
||||
|
||||
- Type of calculation, possible values are: jinja, variable, information, suffix or index
|
||||
- jinja
|
||||
* - Jinja
|
||||
* - Jinja
|
||||
- **jinja**
|
||||
|
||||
|
||||
`string`
|
||||
|
||||
|
||||
`mandatory`
|
||||
- Template Jinja. For a multiple variable, each line represents a value.
|
||||
- `{% if rougail.variable %}
|
||||
- `{% if rougail.variable %}`
|
||||
|
||||
{{ rougail.variable }}
|
||||
`{{ rougail.variable }}`
|
||||
|
||||
{% endif %}`
|
||||
* - Jinja
|
||||
- **params**
|
||||
|
||||
`list`
|
||||
`{% endif %}`
|
||||
* - Jinja
|
||||
- **params**
|
||||
|
||||
`list`
|
||||
- Additional parameters passed to the Jinja template
|
||||
-
|
||||
-
|
||||
* - Variable (`mandatory`)
|
||||
|
||||
Information
|
||||
- **variable**
|
||||
- **variable**
|
||||
|
||||
`string`
|
||||
- Name of associated variable
|
||||
- rougail.variable
|
||||
- rougail.variable
|
||||
* - Variable
|
||||
- **propertyerror**
|
||||
|
||||
- **propertyerror**
|
||||
|
||||
`boolean`
|
||||
- If access to the variable is not possible due to a property (for example `disabled`) by default an error is returned. If the attribute is `false`, the calculated value is empty.
|
||||
|
||||
**Default value:** `true`
|
||||
- false
|
||||
|
||||
- false
|
||||
|
||||
* - Information
|
||||
- **information**
|
||||
|
||||
|
||||
`string`
|
||||
|
||||
|
||||
`mandatory`
|
||||
- Name of the information whose value we want to retrieve.
|
||||
- doc
|
||||
|
|
@ -98,64 +98,147 @@ There are two types of parameter:
|
|||
- parameter via a suffix: in the case of a variable in a dynamic family
|
||||
- parameter via an index: in the case of a follower variable
|
||||
|
||||
.. list-table::
|
||||
.. list-table::
|
||||
:widths: 15 25 20 15
|
||||
:header-rows: 1
|
||||
|
||||
* - Parameter type
|
||||
|
||||
* - Parameter type
|
||||
- Parameter
|
||||
- Comments
|
||||
- Sample
|
||||
|
||||
* -
|
||||
* -
|
||||
- **name**
|
||||
|
||||
|
||||
`string`
|
||||
|
||||
|
||||
`mandatory`
|
||||
- parameter's name
|
||||
- my_param
|
||||
* -
|
||||
- my_param
|
||||
* -
|
||||
- **type**
|
||||
|
||||
|
||||
`string`
|
||||
|
||||
|
||||
`mandatory`
|
||||
- parameter's type, possible values are: variable, information, suffix or index
|
||||
- suffix
|
||||
* - Variable
|
||||
- **variable**
|
||||
|
||||
|
||||
`string`
|
||||
|
||||
|
||||
`mandatory`
|
||||
|
||||
- Variable's name
|
||||
|
||||
- Variable's name
|
||||
- rougail.variable
|
||||
* - Variable (`mandatory`) information
|
||||
- **propertyerror**
|
||||
|
||||
- **propertyerror**
|
||||
|
||||
`boolean`
|
||||
- If access to the variable is not possible due to a property (for example `disabled`) by default an error is returned. If the attribute is `False`, the parameter is not passed to the Jinja template.
|
||||
- **Default value**: `True`
|
||||
* - Variable
|
||||
* - Variable
|
||||
- **optional**
|
||||
|
||||
|
||||
`boolean`
|
||||
- The variable may not exist depending on YAML file imports.
|
||||
- The variable may not exist depending on YAML file imports.
|
||||
If the optional parameter is `True`, the parameter will simply be deleted if the variable does not exist.
|
||||
|
||||
Default value : `False`
|
||||
Default value : `False`
|
||||
- True
|
||||
* - Information
|
||||
- **information**
|
||||
|
||||
|
||||
`string`
|
||||
|
||||
|
||||
`mandatory`
|
||||
- Name of the information whose value we want to retrieve.
|
||||
- doc
|
||||
|
||||
|
||||
The variable path
|
||||
-----------------
|
||||
|
||||
Normal family
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
The default namespace is defined in RougailConfig["variable_namespace"] with the default value "rougail".
|
||||
In addition, there are extras namespaces defined with in RougailConfig["extra_dictionaries"].
|
||||
|
||||
Inside those namespaces we can add families and variables.
|
||||
|
||||
Here is an hierarchic examples:
|
||||
|
||||
.. code-block::
|
||||
|
||||
rougail
|
||||
├── variable1
|
||||
├── family1
|
||||
│ ├── variable2
|
||||
│ └── variable3
|
||||
└── family2
|
||||
└── subfamily1
|
||||
└── variable4
|
||||
extra1
|
||||
└── family3
|
||||
├── variable5
|
||||
└── variable6
|
||||
|
||||
In `calculation` we can use other variables.
|
||||
|
||||
Here is all paths:
|
||||
|
||||
- rougail.variable1
|
||||
- rougail.family1.variable2
|
||||
- rougail.family1.variable3
|
||||
- rougail.family2.subfamily1.variable4
|
||||
- extra1.family3.variable5
|
||||
- extra1.family3.variable6
|
||||
|
||||
Inside a variable's `calculation` we can use relative path. "_" means that other variable is in same family. "__" means that other variables are in parent family, and so on...
|
||||
|
||||
For example, in variable2's `calculation`, we can use relative path:
|
||||
|
||||
- __.variable1
|
||||
- _.variable3
|
||||
- __.family2.subfamily1.variable4
|
||||
|
||||
But we cannot access to extra1 variables with relative path.
|
||||
|
||||
Dynamic family
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Hire is a dynamic family "{{ suffix }}":
|
||||
|
||||
.. code-block::
|
||||
|
||||
rougail
|
||||
├── variable1: ["val1", "val2"]
|
||||
├── {{ suffix }}
|
||||
│ ├── variable2
|
||||
│ └── variable3
|
||||
└── family
|
||||
└── variable4
|
||||
|
||||
For variable2's calculation, we can use:
|
||||
|
||||
- rougail.{{ suffix }}.variable3
|
||||
- _.variable3
|
||||
|
||||
In this case, we get value for "variable3" with the same suffix as "variable2".
|
||||
|
||||
For variable4's calculation, we have two possibility:
|
||||
|
||||
- retrieves all values with all suffixes:
|
||||
|
||||
- rougail.{{ suffix }}.variable3
|
||||
- __.{{ suffix }}.variable3
|
||||
|
||||
- retrieves a value for a specified suffix:
|
||||
|
||||
- rougail.val1.variable3
|
||||
- __.val1.variable3
|
||||
|
||||
Examples
|
||||
-----------
|
||||
|
||||
|
|
@ -167,46 +250,46 @@ Let's start with an example from a simple Jinja template:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_calculated_variable:
|
||||
default:
|
||||
type: jinja
|
||||
jinja: 'no'
|
||||
|
||||
Here is a second example with a boolean variable:
|
||||
Here is a second example with a boolean variable:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_calculated_variable:
|
||||
type: boolean
|
||||
default:
|
||||
type: jinja
|
||||
jinja: 'false'
|
||||
|
||||
And a multiple value of the number type:
|
||||
And a multiple value of the integer type:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_calculated_variable:
|
||||
type: number
|
||||
type: integer
|
||||
multi: true
|
||||
default:
|
||||
type: jinja
|
||||
jinja: |
|
||||
1
|
||||
2
|
||||
3
|
||||
3
|
||||
|
||||
Let's create a variable whose value is returned by a python function:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_calculated_variable:
|
||||
default:
|
||||
type: jinja
|
||||
|
|
@ -214,7 +297,7 @@ Let's create a variable whose value is returned by a python function:
|
|||
|
||||
Then let's create the `return_no` function:
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: python
|
||||
|
||||
def return_no():
|
||||
return 'no'
|
||||
|
|
@ -224,7 +307,7 @@ An example with parameters:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_calculated_variable:
|
||||
description: my description
|
||||
default:
|
||||
|
|
@ -235,19 +318,19 @@ An example with parameters:
|
|||
param1: value
|
||||
param2:
|
||||
type: variable
|
||||
variable: rougail.unknown_variable
|
||||
variable: _.unknown_variable
|
||||
optional: true
|
||||
param3:
|
||||
type: information
|
||||
information: doc
|
||||
variable: rougail.my_calculated_variable
|
||||
variable: _.my_calculated_variable
|
||||
|
||||
An example with a `suffix` type parameter:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
varname:
|
||||
multi: true
|
||||
default:
|
||||
|
|
@ -255,7 +338,9 @@ An example with a `suffix` type parameter:
|
|||
- val2
|
||||
my_dyn_family_:
|
||||
type: dynamic
|
||||
variable: rougail.varname
|
||||
dynamic:
|
||||
type: variable
|
||||
variable: _.varname
|
||||
description: 'Describe '
|
||||
my_dyn_var:
|
||||
type: string
|
||||
|
|
@ -275,7 +360,7 @@ An example with an index type parameter:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
family:
|
||||
type: leadership
|
||||
leader:
|
||||
|
|
@ -294,12 +379,12 @@ An example with an index type parameter:
|
|||
Calculation via a variable
|
||||
-----------------------------
|
||||
|
||||
Copy a variable in another:
|
||||
Copy a variable in another:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_variable:
|
||||
multi: true
|
||||
default:
|
||||
|
|
@ -309,14 +394,32 @@ Copy a variable in another:
|
|||
multi: true
|
||||
default:
|
||||
type: variable
|
||||
variable: rougail.my_variable
|
||||
variable: _.my_variable
|
||||
|
||||
Copy the default value from a variable, means copy type, params and multi attribute too if not define in second variable.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: 1.1
|
||||
my_variable:
|
||||
multi: true
|
||||
type: domainname
|
||||
params:
|
||||
allow_ip: true
|
||||
my_calculated_variable:
|
||||
default:
|
||||
type: variable
|
||||
variable: _.var1
|
||||
|
||||
Here my_calculated_variable is a domainname variable with parameter allow_ip=True and multi to true.
|
||||
|
||||
Copy one variable to another if the source has no `property` problem:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_variable:
|
||||
default: val1
|
||||
disabled: true
|
||||
|
|
@ -324,7 +427,7 @@ Copy one variable to another if the source has no `property` problem:
|
|||
multi: true
|
||||
default:
|
||||
type: variable
|
||||
variable: rougail.my_variable
|
||||
variable: _.my_variable
|
||||
propertyerror: false
|
||||
|
||||
Copy two non-multiple variables into a multiple variable:
|
||||
|
|
@ -332,7 +435,7 @@ Copy two non-multiple variables into a multiple variable:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_variable_1:
|
||||
default: val1
|
||||
my_variable_2:
|
||||
|
|
@ -341,9 +444,9 @@ Copy two non-multiple variables into a multiple variable:
|
|||
multi: true
|
||||
default:
|
||||
- type: variable
|
||||
variable: rougail.my_variable_1
|
||||
variable: _.my_variable_1
|
||||
- type: variable
|
||||
variable: rougail.my_variable_2
|
||||
variable: _.my_variable_2
|
||||
|
||||
A variable in a dynamic family can also be used in a calculation.
|
||||
|
||||
|
|
@ -352,7 +455,7 @@ For example using the variable for a particular suffix:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
varname:
|
||||
multi: true
|
||||
default:
|
||||
|
|
@ -360,7 +463,9 @@ For example using the variable for a particular suffix:
|
|||
- val2
|
||||
my_dyn_family_:
|
||||
type: dynamic
|
||||
variable: rougail.varname
|
||||
dynamic:
|
||||
type: variable
|
||||
variable: _.varname
|
||||
description: 'Describe '
|
||||
my_dyn_var_:
|
||||
type: string
|
||||
|
|
@ -369,7 +474,7 @@ For example using the variable for a particular suffix:
|
|||
all_dyn_var:
|
||||
default:
|
||||
type: variable
|
||||
variable: rougail.my_dyn_family_val1.my_dyn_var_val1
|
||||
variable: _.my_dyn_family_val1.my_dyn_var_val1
|
||||
|
||||
In this case, we recover the value `val1`.
|
||||
|
||||
|
|
@ -378,7 +483,7 @@ Second example using the variable for all suffixes:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
varname:
|
||||
multi: true
|
||||
default:
|
||||
|
|
@ -386,7 +491,9 @@ Second example using the variable for all suffixes:
|
|||
- val2
|
||||
my_dyn_family_:
|
||||
type: dynamic
|
||||
variable: rougail.varname
|
||||
dynamic:
|
||||
type: variable
|
||||
variable: _.varname
|
||||
description: 'Describe '
|
||||
my_dyn_var_:
|
||||
type: string
|
||||
|
|
@ -396,7 +503,7 @@ Second example using the variable for all suffixes:
|
|||
multi: true
|
||||
default:
|
||||
type: variable
|
||||
variable: rougail.my_dyn_family_.my_dyn_var_
|
||||
variable: _.my_dyn_family_.my_dyn_var_
|
||||
|
||||
In this case, we recover the `val1` and `val2` list.
|
||||
|
||||
|
|
@ -406,7 +513,7 @@ Calculation via a suffix
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
varname:
|
||||
multi: true
|
||||
default:
|
||||
|
|
@ -414,7 +521,9 @@ Calculation via a suffix
|
|||
- val2
|
||||
my_dyn_family_:
|
||||
type: dynamic
|
||||
variable: rougail.varname
|
||||
dynamic:
|
||||
type: variable
|
||||
variable: _.varname
|
||||
description: 'Describe '
|
||||
my_dyn_var_:
|
||||
type: string
|
||||
|
|
@ -427,7 +536,7 @@ Calculation via an index
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
family:
|
||||
type: leadership
|
||||
leader:
|
||||
|
|
@ -436,7 +545,7 @@ Calculation via an index
|
|||
- val1
|
||||
- val2
|
||||
follower1:
|
||||
type: number
|
||||
type: integer
|
||||
default:
|
||||
type: index
|
||||
|
||||
|
|
@ -448,7 +557,7 @@ In a first dictionary, let's declare our variable and our calculation:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_calculated_variable:
|
||||
default:
|
||||
type: jinja
|
||||
|
|
@ -459,7 +568,7 @@ In a second dictionary, it is possible to redefine the calculation:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_calculated_variable:
|
||||
redefine: true
|
||||
default:
|
||||
|
|
@ -471,7 +580,7 @@ In a third dictionary, we even can delete the calculation if needed:
|
|||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
my_calculated_variable:
|
||||
redefine: true
|
||||
default: null
|
||||
|
|
|
|||
|
|
@ -1,192 +1,80 @@
|
|||
.. |Tiramisu| replace:: Tiramisu
|
||||
.. _tiramisu: https://forge.cloud.silique.fr/stove/tiramisu
|
||||
|
||||
Getting started
|
||||
====================
|
||||
|
||||
What is a consistency handling system ?
|
||||
------------------------------------------------
|
||||
|
||||
.. questions:: Question: "OK, I have understood that the Rougail stuff enables me to take advantage of |Tiramisu|. But what is all this for? What is exactly a consistency handling system? And again, what is this |Tiramisu| library used for?"
|
||||
|
||||
*Answer*: Well, let's explain what |Tiramisu| is and how we are using the |Tiramisu| library.
|
||||
|
||||
.. glossary::
|
||||
|
||||
Tiramisu
|
||||
|
||||
|Tiramisu| is a consistency handling system that was initially designed
|
||||
in the configuration management scope. To put it more simply,
|
||||
this library is generally used to handle configuration options.
|
||||
|
||||
It manages variables and group of variables. In the Tiramisu scope we call
|
||||
it *options* and *option descriptions*.
|
||||
|
||||
In the Rougail scope, we call it :term:`variable`\ s and :term:`families`.
|
||||
In Rougail, the families and variables are located in the :term:`dictionaries`.
|
||||
|
||||
And this is what we are going to explain in this page.
|
||||
|
||||
The dictionaries
|
||||
---------------------
|
||||
|
||||
.. glossary::
|
||||
|
||||
dictionary
|
||||
dictionaries
|
||||
|
||||
A dictionary in the Rougail meaning is a YAML file that describes variables
|
||||
and their dependencies / consistencies.
|
||||
There can be a lot of dictionary files located in many different folders.
|
||||
|
||||
Rougail reads all the dictionaries and loads them into a single object
|
||||
that handles the variables consistency.
|
||||
|
||||
.. image:: images/schema.png
|
||||
|
||||
The main advantage is that declaring variables and writing consistency is a simple
|
||||
as writing YAML. It is not necessary to write :term:`Tiramisu` code.
|
||||
It simplifies a lot of things.
|
||||
|
||||
And rather than writing :term:`Tiramisu` code, we can declare variables and describe the relationships between variables in a declarative mode.
|
||||
|
||||
Once the dictionaries are loaded by Rougail, we find all the power of the :term:`Tiramisu` configuration management tool.
|
||||
|
||||
The dictionaries YAML format
|
||||
---------------------------------
|
||||
|
||||
Before getting started with Rougail we need to learn the specifics of the YAML dictionaries file format (as well as some templating concepts).
|
||||
|
||||
.. FIXME parler de jinja https://jinja.palletsprojects.com
|
||||
|
||||
Here is a :term:`dictionary` example:
|
||||
|
||||
.. code-block:: yaml
|
||||
:linenos:
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
proxy:
|
||||
description: Configure Proxy Access to the Internet
|
||||
type: family
|
||||
|
||||
Line 3, we declare a **variable** named `proxy` with his `description` line 4 and his `type` line 5.
|
||||
|
||||
The variables
|
||||
-----------------
|
||||
|
||||
Here is a :term:`structure file` example with only a variable **variable** named `proxy_mode`
|
||||
A variable can be defined without other informations.
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/firefox/00-proxy.yml
|
||||
:language: yaml
|
||||
:caption: A Rougail structure file with a variable named `proxy_mode`, with a description.
|
||||
:name: RougailStructureFirstVariable
|
||||
|
||||
..
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
proxy_mode:
|
||||
...
|
||||
|
||||
But it's better to describe this variable:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_011/firefox/00-proxy.yml
|
||||
:language: yaml
|
||||
:caption: A Rougail structure file with a variable named `proxy_mode`, with a description.
|
||||
:name: RougailStructureFirstVariableWithDescription
|
||||
|
||||
..
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
proxy_mode: # Configure Proxy Access to the Internet
|
||||
...
|
||||
|
||||
|
||||
The same with a default value:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_012/firefox/00-proxy.yml
|
||||
:language: yaml
|
||||
:caption: A Rougail structure file with a variable named `proxy_mode`, with a default value.
|
||||
:name: RougailStructureFirstVariableDefault
|
||||
|
||||
..
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
proxy_mode: No proxy # Configure Proxy Access to the Internet
|
||||
...
|
||||
|
||||
variable
|
||||
|
||||
Here is a second definition of a :term:`variable`: it is a declaration unit that represents a business domain metaphor,
|
||||
|
||||
the most common example is that a variable represents a configuration option
|
||||
A :term:`variable` is a declaration unit that represents a business domain metaphor,
|
||||
the most common example is that a variable that represents a configuration option
|
||||
in a application, but a variable represents something more that a configuration option.
|
||||
It provides a business domain specific representation unit.
|
||||
|
||||
.. note:: dictionaries can just define a list of variables, but we will see that
|
||||
we can specify a lot more. We can define variables **and** their relations,
|
||||
and the consistency between them.
|
||||
|
||||
In the next step, we will explain through a tutorial how to construct a list of variables.
|
||||
|
||||
Families of variables
|
||||
--------------------------
|
||||
|
||||
.. glossary::
|
||||
family
|
||||
|
||||
family
|
||||
families
|
||||
A :term:`family` is simply a container of variables and/ore some subfamilies.
|
||||
|
||||
A family of variables is simply a collection of variables that refer to
|
||||
the same business model category. It's just a variables container.
|
||||
Think of it as a container as well as a namespace.
|
||||
|
||||
A "hello world" with Rougail
|
||||
------------------------------
|
||||
|
||||
We're gonna make the simplest possible example.
|
||||
|
||||
.. prerequisites:: Prerequisites
|
||||
|
||||
We assume that Rougail's library is installed on your computer (or in a virtual environment).
|
||||
|
||||
.. exercise:: Let's make a Hello world
|
||||
|
||||
Here is the tree structure we want to have::
|
||||
|
||||
workplace
|
||||
├── dict
|
||||
│ ├── hello.yml
|
||||
└── hello.py
|
||||
|
||||
- Let's make a :file:`workplace` directory, with a :file:`dict` subfolder.
|
||||
- First, we need a :term:`dictionary`, so let's make the :file:`hello.yml` file
|
||||
which is located in the :file:`dict` subfolder, with the following content:
|
||||
Here how a YAML structure file with a family looks like:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: The `hello.yaml` file
|
||||
:caption: A :file:`hello.yml` structure sample file
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
hello:
|
||||
default: world
|
||||
|
||||
- Then we make a :file:`hello.py` in our root :file:`workplace` directory:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: The :file:`hello.py` file
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
|
||||
RougailConfig['dictionaries_dir'] = ['dict']
|
||||
rougail = Rougail()
|
||||
config = rougail.get_config()
|
||||
print(config.value.get())
|
||||
|
||||
.. demo:: Let's run the :file:`hello.py` script
|
||||
|
||||
We launch the script:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python hello.py
|
||||
|
||||
And we obtain the following result:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{'rougail.hello': 'world'}
|
||||
|
||||
**Congratulations ! You have successfully completed your first Rougail script.**
|
||||
|
||||
A "Hello, <name> " sample with a family
|
||||
------------------------------------------
|
||||
|
||||
Let's continuing on our "Hello world" theme and add a :term:`family` container.
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`hello.yml` file
|
||||
:linenos:
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
world:
|
||||
description: Hello world family container
|
||||
name:
|
||||
description: Somebody to say hello
|
||||
default: rougail
|
||||
|
||||
Here, we have a family named `world`.
|
||||
Here, we have a family named `world`.
|
||||
This family contains a variable named `name`
|
||||
|
||||
Again, let's validate this YAML file against Rougail's API:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python hello.py
|
||||
|
||||
We then have the output:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
{'rougail.world.name': 'rougail'}
|
||||
|
|
|
|||
BIN
docs/images/QuestionaryChoice.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
docs/images/UserDataOutput.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
docs/images/read_write.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 26 KiB |
BIN
docs/images/tiramisu_get_set.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
|
|
@ -8,57 +8,84 @@
|
|||
Rougail
|
||||
===========
|
||||
|
||||
.. todo:: définir les termes suivants
|
||||
|
||||
family.rst:25: WARNING: label non défini: 'convention on variable names'
|
||||
/family.rst:114: WARNING: term not in glossary: 'calculation'
|
||||
/variable.rst:39: WARNING: label non défini: 'convention on variable names'
|
||||
/variable.rst:83: WARNING: term not in glossary: 'leading'
|
||||
/variable.rst:100: WARNING: term not in glossary: 'required'
|
||||
/variable.rst:102: WARNING: term not in glossary: 'leader'
|
||||
/variable.rst:102: WARNING: term not in glossary: 'follower'
|
||||
/variable.rst:126: WARNING: term not in glossary: 'multiple'
|
||||
/variable.rst:126: WARNING: term not in glossary: 'multiple'
|
||||
/variable.rst:131: WARNING: term not in glossary: 'calculation'
|
||||
/variable.rst:143: WARNING: term not in glossary: 'calculation'
|
||||
/variable.rst:148: WARNING: term not in glossary: 'calculation'
|
||||
/variable.rst:153: WARNING: term not in glossary: 'calculation'
|
||||
/dictionary.rst:9: WARNING: term not in glossary: 'templates'
|
||||
/dictionary.rst:19: WARNING: term not in glossary: 'redefine'
|
||||
/dictionary.rst:24: WARNING: term not in glossary: 'variable_namespace'
|
||||
|
||||
|
||||
.. image:: images/logo.png
|
||||
|
||||
- is a `delicious cooked dish <https://fr.wikipedia.org/wiki/Rougail>`_ from the Mauritius and Reunion Islands,
|
||||
- is a `delicious cooked dish <https://fr.wikipedia.org/wiki/Rougail>`_ from the Mauritius and Reunion Islands,
|
||||
|
||||
- it is also a `Python3 <https://www.python.org/>`_ library which enables us to conveniently load application :term:`variable`\ s in a simple `YAML <https://yaml.org/>`_ format in such a way that the end user consumer can handle them consistently (that is, against an user-defined consistency).
|
||||
|
||||
In other words, using Rougail in your application or your python libraries can tansform end user consumer defined consistency rules into highly consistent business objects.
|
||||
|
||||
We then have to say that the handling system used to ensure the variables integrity is another python library, called :term:`Tiramisu`. Rougail is currently strongly affiliated with Tiramisu.
|
||||
|
||||
.. note:: Rougail is currently intended to work in coordination with :term:`Tiramisu` and **is not** intended to be connected with any other consistency handling system.
|
||||
|
||||
Explained differently, Rougail allows you to easily implement an integration of the powerful tiramisu consistency handling system.
|
||||
- it is also a `Python <https://www.python.org/>`_ library which enables us to conveniently load application :term:`variable`\ s in a simple `YAML <https://yaml.org/>`_ format in such a way that the end user consumer can handle them consistently (that is, against an user-defined consistency).
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:caption: Getting started
|
||||
:caption: What is it all about
|
||||
|
||||
gettingstarted
|
||||
tutorial
|
||||
concepts
|
||||
tutorial/index
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:caption: The library
|
||||
|
||||
library
|
||||
configuration
|
||||
:caption: The structured files
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:caption: The dictionaries
|
||||
structfile
|
||||
naming_convention
|
||||
|
||||
dictionary
|
||||
dict_convention
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:caption: The variables
|
||||
|
||||
variable
|
||||
family
|
||||
tags
|
||||
fill
|
||||
Value checks <check>
|
||||
condition
|
||||
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:caption: Load values from user datas
|
||||
|
||||
user_datas/index
|
||||
configuration
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:caption: The library
|
||||
|
||||
library/index
|
||||
configuration
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:caption: Notes
|
||||
|
||||
developer
|
||||
|
||||
developer
|
||||
documentation
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
install
|
||||
naming_convention
|
||||
|
||||
.. rubric:: Index page
|
||||
|
||||
- :ref:`genindex`
|
||||
|
|
|
|||
35
docs/install.rst
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
.. _installation:
|
||||
|
||||
Rougail library installation
|
||||
======================================
|
||||
|
||||
Activate you virtual environment
|
||||
------------------------------------
|
||||
|
||||
- Open a shell session
|
||||
|
||||
- install the virtual environment: `python -m'venv' .venv`
|
||||
- activate it `./.venv/bin/activate` (or `.venv\Scripts\activate.exe` under windows)
|
||||
|
||||
Standard installation
|
||||
---------------------------
|
||||
|
||||
You can use the `pip` python installer, here is the install command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install rougail
|
||||
|
||||
Installation of third-party libraries
|
||||
-------------------------------------------
|
||||
|
||||
First, download the :download:`requirements.txt file: <install/requirements.txt>`
|
||||
|
||||
.. literalinclude:: install/requirements.txt
|
||||
:caption: The :file:`requirements.txt` requirements file
|
||||
|
||||
Then in your virtual environment, recursively install the third-party libraries as follows:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install -r requirements.txt --extra-index-url https://test.pypi.org/simple/
|
||||
13
docs/install/requirements.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
rougail==1.2.0a43
|
||||
rougail-cli==0.2.0a33
|
||||
rougail-output-ansible==0.2.0a18
|
||||
rougail-output-console==0.2.0a19
|
||||
rougail-output-doc==0.2.0a39
|
||||
rougail-output-formatter==0.1.0a22
|
||||
rougail-output-json==0.2.0a14
|
||||
rougail-user-data-bitwarden==0.1.0a26
|
||||
rougail-user-data-environment==0.1.0a15
|
||||
rougail-user-data-questionary==0.1.0a1
|
||||
rougail-user-data-yaml==0.2.0a16
|
||||
tiramisu==5.2.0a17
|
||||
tiramisu-cmdline-parser==0.7.0a4
|
||||
192
docs/library.rst
|
|
@ -1,192 +0,0 @@
|
|||
`Rougail`'s library description
|
||||
=================================
|
||||
|
||||
Rougail is a configuration management library that allows you to load variables in a simple and convenient way.
|
||||
|
||||
In the following examples, we will use a specific configuration of Rougail. You will find all the options to :doc:`customize the directories structure used <configuration>`.
|
||||
|
||||
To load the configuration you must import the `RougailConfig` class and set the `dictionaries_dir` values:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from rougail import RougailConfig
|
||||
|
||||
RougailConfig['dictionaries_dir'] = ['dict']
|
||||
|
||||
Let's convert a dictionary
|
||||
-----------------------------
|
||||
|
||||
As a reminder, a :term:`dictionary` is a set of instructions that will allow us to create :term:`families` and :term:`variables`.
|
||||
|
||||
Let's start by creating a simple dictionary.
|
||||
|
||||
Here is a first :file:`dict/00-base.yml` dictionary:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
my_variable:
|
||||
default: my_value
|
||||
|
||||
Then, let's create the :term:`Tiramisu` objects via the following script:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the `script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
|
||||
RougailConfig['dictionaries_dir'] = ['dict']
|
||||
rougail = Rougail()
|
||||
config = rougail.get_config()
|
||||
print(config.value.get())
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
{'rougail.my_variable': 'my_value'}
|
||||
|
||||
Let's convert an extra dictionary
|
||||
-------------------------------------
|
||||
|
||||
.. index:: extras
|
||||
|
||||
The default namespace for variables and families is `rougail`. It is possible to define other namespaces. These additional namespaces are called `extras`.
|
||||
|
||||
.. FIXME: faire une page pour les extras
|
||||
|
||||
Additional namespaces are defined during configuration.
|
||||
|
||||
For example, here's how to add an `example` namespace:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
RougailConfig['extra_dictionaries']['example'] = ['extras/']
|
||||
|
||||
Then let's create an extra :term:`dictionary` :file:`extras/00-base.yml`:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`extras/00-base.yml` file content
|
||||
---
|
||||
version: '1.0'
|
||||
my_variable_extra:
|
||||
default: my_value_extra
|
||||
|
||||
Then, let's create the :term:`Tiramisu` objects via the following :file:`script.py` script:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
|
||||
RougailConfig['dictionaries_dir'] = ['dict']
|
||||
RougailConfig['extra_dictionaries']['example'] = ['extras/']
|
||||
rougail = Rougail()
|
||||
config = rougail.get_config()
|
||||
print(config.value.dict())
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
{'rougail.my_variable': 'my_value', 'example.my_variable_extra': 'my_value_extra'}
|
||||
|
||||
Let's create a custom function
|
||||
----------------------------------
|
||||
|
||||
We create the complementary :term:`dictionary` named :file:`dict/01-function.yml` so that the `my_variable_jinja` variable is :term:`calculated`:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
my_variable_jinja:
|
||||
type: "string"
|
||||
default:
|
||||
type: jinja
|
||||
jinja: "{{ return_no() }}"
|
||||
|
||||
Then let's define the :func:`return_no` function in :file:`functions.py`:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`functions.py` content
|
||||
|
||||
def return_no():
|
||||
return 'no'
|
||||
|
||||
Then, let's create the :term:`Tiramisu` objects via the following script:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the `script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
|
||||
RougailConfig['dictionaries_dir'] = ['dict']
|
||||
RougailConfig['extra_dictionaries']['example'] = ['extras/']
|
||||
RougailConfig['functions_file'] = 'functions.py'
|
||||
rougail = Rougail()
|
||||
config = rougail.get_config()
|
||||
print(config.value.dict())
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
{'rougail.my_variable': 'my_value', 'rougail.my_variable_jinja': 'no', 'example.my_variable_extra': 'my_value_extra'}
|
||||
|
||||
The value of the `my_variable_extra` variable is calculated, and it's value comes from the :func:`return_no` function.
|
||||
|
||||
|
||||
Create your own type
|
||||
----------------------
|
||||
|
||||
A variable has a type. This type enables the variable to define the values that are accepted by this variable.
|
||||
|
||||
There is a series of default types, but obviously not all cases are taken.
|
||||
|
||||
It's possible to create your own type.
|
||||
|
||||
Here an example to a lipogram option (in a string, we cannot use "e" character):
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the `lipogram.py` file content
|
||||
|
||||
from tiramisu import StrOption
|
||||
class LipogramOption(StrOption):
|
||||
__slots__ = tuple()
|
||||
_type = 'lipogram'
|
||||
|
||||
def validate(self,
|
||||
value):
|
||||
super().validate(value)
|
||||
# verify that there is any 'e' in the sentense
|
||||
if 'e' in value:
|
||||
raise ValueError('Perec wrote a book without any "e", you could not do it in a simple sentence?')
|
||||
|
||||
To add the new lipogram type in Rougail:
|
||||
|
||||
.. code-block:: python
|
||||
>>> from rougail import Rougail, RougailConfig
|
||||
>>> RougailConfig['dictionaries_dir'] = ['dict']
|
||||
>>> RougailConfig['custom_types']['lipogram'] = LipogramOption
|
||||
|
||||
Now, we can use lipogram type.
|
||||
Here is a :file:`dict/00-base.yml` dictionary:
|
||||
|
||||
.. code-block:: yaml
|
||||
---
|
||||
version: '1.0'
|
||||
var:
|
||||
type: lipogram
|
||||
|
||||
.. code-block:: python
|
||||
>>> rougail = Rougail()
|
||||
>>> config = rougail.get_config()
|
||||
>>> config.option('rougail.var').value.set('blah')
|
||||
>>> config.option('rougail.var').value.set('I just want to add a quality string that has no bad characters')
|
||||
[...]
|
||||
tiramisu.error.ValueOptionError: "I just want to add a quality string that has no bad characters" is an invalid lipogram for "var", Perec wrote a book without any "e", you could not do it in a simple sentence?
|
||||
47
docs/library/custom_function.rst
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
Let's create a custom function
|
||||
==============================
|
||||
|
||||
We create the complementary :term:`structure file` named :file:`dict/01-function.yml` so that the `my_variable_jinja` variable is :term:`calculated`:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable_jinja:
|
||||
default:
|
||||
type: jinja
|
||||
jinja: "{{ return_no() }}"
|
||||
...
|
||||
|
||||
Then let's define the :func:`return_no` function in :file:`functions.py`:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`functions.py` content
|
||||
|
||||
def return_no():
|
||||
return 'no'
|
||||
|
||||
Then, let's create the :term:`Tiramisu` objects via the following script:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the `script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
|
||||
RougailConfig['main_structural_directories'] = ['dict']
|
||||
RougailConfig['extra_namespaces']['example'] = ['extras/']
|
||||
RougailConfig['functions_file'] = 'functions.py'
|
||||
rougail = Rougail()
|
||||
config = rougail.get_config()
|
||||
print(config.value.dict())
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
{'rougail.my_variable': 'my_value', 'rougail.my_variable_jinja': 'no', 'example.my_variable_extra': 'my_value_extra'}
|
||||
|
||||
The value of the `my_variable_extra` variable is calculated, and it's value comes from the :func:`return_no` function.
|
||||
45
docs/library/extra.rst
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
Let's convert an extra namespace structural file
|
||||
================================================
|
||||
|
||||
.. index:: extras
|
||||
|
||||
The default namespace for variables and families is `rougail`. It is possible to define other namespaces. These additional namespaces are called `extras`.
|
||||
|
||||
.. FIXME: faire une page pour les extras
|
||||
|
||||
Additional namespaces are defined during configuration.
|
||||
|
||||
For example, here's how to add an `example` namespace:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
RougailConfig['extra_namespaces']['example'] = ['extras/']
|
||||
|
||||
Then let's create an extra :term:`structure file` :file:`extras/00-base.yml`:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`extras/00-base.yml` file content
|
||||
---
|
||||
version: '1.1'
|
||||
my_variable_extra:
|
||||
default: my_value_extra
|
||||
|
||||
Then, let's create the :term:`Tiramisu` objects via the following :file:`script.py` script:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
|
||||
RougailConfig['main_structural_directories'] = ['dict/']
|
||||
RougailConfig['extra_namespaces']['example'] = ['extras/']
|
||||
rougail = Rougail()
|
||||
config = rougail.get_config()
|
||||
print(config.value.dict())
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
{'rougail.my_variable': 'my_value', 'example.my_variable_extra': 'my_value_extra'}
|
||||
87
docs/library/index.rst
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
`Rougail`'s library description
|
||||
=================================
|
||||
|
||||
Rougail is a configuration management library that allows you to load variables in a simple and convenient way.
|
||||
|
||||
In the following examples, we will use a specific configuration of Rougail.
|
||||
|
||||
|
||||
.. FIXME: You will find all the configuration options in doc:`configuration`
|
||||
find a document with all the configuration options
|
||||
|
||||
To load the configuration you must import the `RougailConfig` class and set the `main_structural_directories` values:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from rougail import RougailConfig
|
||||
|
||||
RougailConfig['main_structural_directories'] = ['dict']
|
||||
|
||||
Let's convert a our first structural file
|
||||
-----------------------------------------
|
||||
|
||||
As a reminder, a :term:`structure file` is a set of instructions that will allow us to create :term:`families <family>` and :term:`variables <variable>`.
|
||||
|
||||
Let's start by creating a simple structure file.
|
||||
|
||||
Here is a first :file:`dict/00-base.yml` structure file:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable: my_value # my variable
|
||||
...
|
||||
|
||||
Then, let's create the :term:`Tiramisu` objects via the following script:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the `script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
|
||||
RougailConfig['main_structural_directories'] = ['dict']
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
print(config.value.get())
|
||||
|
||||
.. demo:: Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
{'rougail.my_variable': 'my_value'}
|
||||
|
||||
The operator role
|
||||
--------------------
|
||||
|
||||
The :term:`operator` role corresponds to the :term:`tiramisu` settings:
|
||||
|
||||
.. image:: ../images/tiramisu_get_set.png
|
||||
|
||||
.. index:: questionary
|
||||
|
||||
But instead of coding in the end user developer way, the opterator will prefer using the Rougail CLI interface:
|
||||
|
||||
.. image:: ../images/QuestionaryChoice.png
|
||||
|
||||
|
||||
The Rougail CLI can output a rather complete view of the dataset:
|
||||
|
||||
.. image:: ../images/UserDataOutput.png
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:caption: Use library
|
||||
|
||||
user_datas
|
||||
output
|
||||
parse
|
||||
tags
|
||||
rougailconfig_load_from_cli
|
||||
extra
|
||||
custom_function
|
||||
own_type
|
||||
upgrade
|
||||
269
docs/library/output.rst
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
Display the result
|
||||
==================
|
||||
|
||||
After construct a configuration, loads user datas, you can choose this configuration in different output format.
|
||||
|
||||
First of create, let's create a structural file like this:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`dist/00-base.yml` file content
|
||||
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable: my value # My first variable
|
||||
|
||||
my_boolean_variable: true # My boolean variable
|
||||
|
||||
my_integer_variable: 1 # My integer variable
|
||||
|
||||
my_secret_variable:
|
||||
description: My secret variable
|
||||
type: secret
|
||||
default: MyVeryStrongPassword
|
||||
...
|
||||
|
||||
Display in a console
|
||||
--------------------
|
||||
|
||||
We can display configuration directly in the console:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.output_console import RougailOutputConsole
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.output"] = "console"
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
config.property.read_only()
|
||||
RougailOutputConsole(config).print()
|
||||
|
||||
.. FIXME display console!
|
||||
|
||||
console.key_is_description
|
||||
''''''''''''''''''''''''''
|
||||
|
||||
By default, the key is the variable description, if you prefer have only the path:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.output_json import RougailOutputJson
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.output"] = "console"
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
config.property.read_only()
|
||||
RougailOutputJson(config).print()
|
||||
|
||||
|
||||
.. FIXME display console!
|
||||
|
||||
console.show_secrets
|
||||
''''''''''''''''''''
|
||||
|
||||
Secrets are remplace by "*******", to display real secrets:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.output_console import RougailOutputConsole
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.output"] = "console"
|
||||
RougailConfig["console.show_secrets"] = True
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
config.property.read_only()
|
||||
RougailOutputConsole(config).print()
|
||||
|
||||
.. FIXME display console!
|
||||
|
||||
console.mandatory
|
||||
'''''''''''''''''
|
||||
|
||||
Before display configuration, mandatories variables are check. If you don't want, add the parameter `console.mandatory` to False:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.output_console import RougailOutputConsole
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.output"] = "console"
|
||||
RougailConfig["console.mandatory"] = False
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
config.property.read_only()
|
||||
RougailOutputConsole(config).print()
|
||||
|
||||
.. FIXME display console!
|
||||
|
||||
JSON
|
||||
----
|
||||
|
||||
Your script can return a JSON object:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.output_console import RougailOutputConsole
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.output"] = "json"
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
config.property.read_only()
|
||||
RougailOutputConsole(config).print()
|
||||
|
||||
Let's try this script:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python script.py
|
||||
{
|
||||
"my_variable": "my value",
|
||||
"my_boolean_variable": true,
|
||||
"my_integer_variable": 1,
|
||||
"my_secret_variable": "MyVeryStrongPassword"
|
||||
}
|
||||
|
||||
ANSIBLE
|
||||
-------
|
||||
|
||||
It's possible to use Ansible has a output format.
|
||||
|
||||
The goal is here to use Ansible has a dynamic user's inventories structure manage by Rougail.
|
||||
|
||||
This output needs an extra namespace, named, by default, "hosts". This namespace define your hosts and groups.
|
||||
|
||||
Let's create a single group "my_group" with one host "group1.net":
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`hosts/00-hosts.yml` file content
|
||||
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
hostnames:
|
||||
|
||||
my_group:
|
||||
|
||||
hosts:
|
||||
type: domainname
|
||||
default:
|
||||
- group1.net
|
||||
...
|
||||
|
||||
Now we can generate Ansible inventory:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
#!/bin/env python
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.output_ansible import RougailOutputAnsible
|
||||
|
||||
RougailConfig["main_namespace"] = "main"
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig['extra_namespaces']['hosts'] = ['hosts/']
|
||||
RougailConfig["step.output"] = "ansible"
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
config.property.read_only()
|
||||
RougailOutputAnsible(config).print()
|
||||
|
||||
We will retrieved all ours variables associate to this group with all variables inside the namespace `main`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python script.py
|
||||
{
|
||||
"_meta": {
|
||||
"hostvars": {
|
||||
"group1.net": {
|
||||
"ansible_host": "group1.net",
|
||||
"main": {
|
||||
"my_variable": "my value",
|
||||
"my_boolean_variable": true,
|
||||
"my_integer_variable": 1,
|
||||
"my_secret_variable": "MyVeryStrongPassword"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"my_group": {
|
||||
"hosts": [
|
||||
"group1.net"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
We can now use our script as an inventory source in Ansible:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ chmod +x script.py
|
||||
$ ansible-inventory -i script.py --list
|
||||
{
|
||||
"_meta": {
|
||||
"hostvars": {
|
||||
"group1.net": {
|
||||
"ansible_host": "group1.net",
|
||||
"main": {
|
||||
"my_boolean_variable": true,
|
||||
"my_integer_variable": 1,
|
||||
"my_secret_variable": "MyVeryStrongPassword",
|
||||
"my_variable": "my value"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"all": {
|
||||
"children": [
|
||||
"ungrouped",
|
||||
"my_group"
|
||||
]
|
||||
},
|
||||
"my_group": {
|
||||
"hosts": [
|
||||
"group1.net"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
DOC
|
||||
---
|
||||
|
||||
We can generate the documentation of all the Rougail variable:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.output_doc import RougailOutputDoc
|
||||
|
||||
RougailConfig["main_namespace"] = "main"
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.output"] = "doc"
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
config.property.read_only()
|
||||
RougailOutputDoc(config).print()
|
||||
|
||||
.. FIXME : display
|
||||
52
docs/library/own_type.rst
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
Create your own type
|
||||
====================
|
||||
|
||||
A variable has a type. This type enables the variable to define the values that are accepted by this variable.
|
||||
|
||||
There is a series of default types, but obviously not all cases are taken.
|
||||
|
||||
It's possible to create your own type.
|
||||
|
||||
Here an example to a lipogram option (in a string, we cannot use "e" character):
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the `lipogram.py` file content
|
||||
|
||||
from tiramisu import StrOption
|
||||
class LipogramOption(StrOption):
|
||||
__slots__ = tuple()
|
||||
_type = 'lipogram'
|
||||
|
||||
def validate(self,
|
||||
value):
|
||||
super().validate(value)
|
||||
# verify that there is any 'e' in the sentense
|
||||
if 'e' in value:
|
||||
raise ValueError('Perec wrote a book without any "e", you could not do it in a simple sentence?')
|
||||
|
||||
To add the new lipogram type in Rougail:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from rougail import Rougail, RougailConfig
|
||||
>>> RougailConfig['main_structural_directories'] = ['dict']
|
||||
>>> RougailConfig['custom_types']['lipogram'] = LipogramOption
|
||||
|
||||
Now, we can use lipogram type.
|
||||
Here is a :file:`dict/00-base.yml` structure file:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.1'
|
||||
var:
|
||||
type: lipogram
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> rougail = Rougail()
|
||||
>>> config = rougail.get_config()
|
||||
>>> config.option('rougail.var').value.set('blah')
|
||||
>>> config.option('rougail.var').value.set('I just want to add a quality string that has no bad characters')
|
||||
[...]
|
||||
tiramisu.error.ValueOptionError: "I just want to add a quality string that has no bad characters" is an invalid lipogram for "var", Perec wrote a book without any "e", you could not do it in a simple sentence?
|
||||
260
docs/library/parse.rst
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
Retrieve all variables and families
|
||||
===================================
|
||||
|
||||
Rougail returns a :term:`Tiramisu` config.
|
||||
|
||||
Let's retrieve our variables and families to manager this.
|
||||
|
||||
First of all, create our structural file:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`dist/00-base.yml` file content
|
||||
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable: # a simple variable
|
||||
- value1
|
||||
- value2
|
||||
|
||||
a_family: # a simple family
|
||||
|
||||
my_variable: my_value # a simple variable inside the family
|
||||
|
||||
a_dyn_family_{{ identifier }}:
|
||||
description: a dynamic family for "{{ identifier }}"
|
||||
dynamic:
|
||||
variable: _.my_variable
|
||||
|
||||
a_leadership:
|
||||
description: a leader family
|
||||
|
||||
a_leader: # a leader variable
|
||||
a_follower: # a follower variable
|
||||
...
|
||||
|
||||
Walk through our config
|
||||
-------------------------
|
||||
|
||||
Create our first script to walk through our config:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
|
||||
RougailConfig['main_structural_directories'] = ["dist/"]
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
def walk(config):
|
||||
for option in config:
|
||||
print(option.description())
|
||||
if option.isoptiondescription():
|
||||
walk(option)
|
||||
|
||||
if __name__ == '__main__':
|
||||
walk(config)
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
rougail
|
||||
rougail.my_variable (a simple variable)
|
||||
rougail.a_family (a simple family)
|
||||
rougail.a_family.my_variable (a simple variable inside the family)
|
||||
rougail.a_dyn_family_value1 (a dynamic family for "value1")
|
||||
rougail.a_dyn_family_value1.a_leadership (a leader family)
|
||||
rougail.a_dyn_family_value1.a_leadership.a_leader (a leader variable)
|
||||
rougail.a_dyn_family_value1.a_leadership.a_follower (a follower variable)
|
||||
rougail.a_dyn_family_value2 (a dynamic family for "value2")
|
||||
rougail.a_dyn_family_value2.a_leadership (a leader family)
|
||||
rougail.a_dyn_family_value2.a_leadership.a_leader (a leader variable)
|
||||
rougail.a_dyn_family_value2.a_leadership.a_follower (a follower variable)
|
||||
|
||||
We retrieve alls description of variables and families.
|
||||
|
||||
Let us distinguish the variables of the families
|
||||
------------------------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
|
||||
RougailConfig['main_structural_directories'] = ["dist/"]
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
def walk(config, level=0):
|
||||
for option in config:
|
||||
if option.isoptiondescription():
|
||||
typ = "family"
|
||||
else:
|
||||
typ = "variable"
|
||||
prefix = " " * level
|
||||
print(f"{prefix}{typ}: {option.description()}")
|
||||
if option.isoptiondescription():
|
||||
walk(option, level + 1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
walk(config)
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
family: rougail
|
||||
variable: rougail.my_variable (a simple variable)
|
||||
family: rougail.a_family (a simple family)
|
||||
variable: rougail.a_family.my_variable (a simple variable inside the family)
|
||||
family: rougail.a_dyn_family_value1 (a dynamic family for "value1")
|
||||
family: rougail.a_dyn_family_value1.a_leadership (a leader family)
|
||||
variable: rougail.a_dyn_family_value1.a_leadership.a_leader (a leader variable)
|
||||
variable: rougail.a_dyn_family_value1.a_leadership.a_follower (a follower variable)
|
||||
family: rougail.a_dyn_family_value2 (a dynamic family for "value2")
|
||||
family: rougail.a_dyn_family_value2.a_leadership (a leader family)
|
||||
variable: rougail.a_dyn_family_value2.a_leadership.a_leader (a leader variable)
|
||||
variable: rougail.a_dyn_family_value2.a_leadership.a_follower (a follower variable)
|
||||
|
||||
Or if we want more precision:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
|
||||
RougailConfig['main_structural_directories'] = ["dist/"]
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
def walk(config, level=0):
|
||||
for option in config:
|
||||
if option.isoptiondescription():
|
||||
if option.isleadership():
|
||||
typ = "leadership"
|
||||
elif option.isdynamic():
|
||||
typ = "dynamic family"
|
||||
else:
|
||||
typ = "family"
|
||||
else:
|
||||
if option.isleader():
|
||||
typ = "leader"
|
||||
elif option.isfollower():
|
||||
typ = "follower"
|
||||
else:
|
||||
typ = "option"
|
||||
if option.isdynamic():
|
||||
typ = f"dynamic {typ}"
|
||||
prefix = " " * level
|
||||
print(f"{prefix}{typ}: {option.description()}")
|
||||
if option.isoptiondescription():
|
||||
walk(option, level + 1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
walk(config)
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
family: rougail
|
||||
option: rougail.my_variable (a simple variable)
|
||||
family: rougail.a_family (a simple family)
|
||||
option: rougail.a_family.my_variable (a simple variable inside the family)
|
||||
dynamic family: rougail.a_dyn_family_value1 (a dynamic family for "value1")
|
||||
dynamic family: rougail.a_dyn_family_value1.a_leadership (a leader family)
|
||||
dynamic option: rougail.a_dyn_family_value1.a_leadership.a_leader (a leader variable)
|
||||
dynamic option: rougail.a_dyn_family_value1.a_leadership.a_follower (a follower variable)
|
||||
dynamic family: rougail.a_dyn_family_value2 (a dynamic family for "value2")
|
||||
dynamic family: rougail.a_dyn_family_value2.a_leadership (a leader family)
|
||||
dynamic option: rougail.a_dyn_family_value2.a_leadership.a_leader (a leader variable)
|
||||
dynamic option: rougail.a_dyn_family_value2.a_leadership.a_follower (a follower variable)
|
||||
|
||||
Get variable values
|
||||
-------------------
|
||||
|
||||
If we want to walk to get variables and their values:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
|
||||
RougailConfig['main_structural_directories'] = ["dist/"]
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
def walk(config):
|
||||
for option in config:
|
||||
if option.isoptiondescription():
|
||||
walk(option)
|
||||
else:
|
||||
print(f"{option.description()}: {option.value.get()}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
walk(config)
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
rougail.my_variable (a simple variable): ['value1', 'value2']
|
||||
rougail.a_family.my_variable (a simple variable inside the family): my_value
|
||||
rougail.a_dyn_family_value1.a_leadership.a_leader (a leader variable): None
|
||||
rougail.a_dyn_family_value1.a_leadership.a_follower (a follower variable): None
|
||||
rougail.a_dyn_family_value2.a_leadership.a_leader (a leader variable): None
|
||||
rougail.a_dyn_family_value2.a_leadership.a_follower (a follower variable): None
|
||||
|
||||
Modify variable values
|
||||
----------------------
|
||||
|
||||
Some variables are mandatories but hasn't value. Here we set alls values:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
RougailConfig['main_structural_directories'] = ["dist/"]
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
|
||||
def walk(config):
|
||||
for option in config:
|
||||
if option.isoptiondescription():
|
||||
walk(option)
|
||||
else:
|
||||
print(f"{option.description()}: {option.value.get()}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Mandatories variables without value:")
|
||||
print(config.value.mandatory())
|
||||
config.value.set("rougail.my_variable", ["value 5", "value 6"])
|
||||
config.value.set("rougail.a_dyn_family_value_5.a_leadership.a_leader", "value 1")
|
||||
config.value.set("rougail.a_dyn_family_value_5.a_leadership.a_follower", "value 2")
|
||||
config.value.set("rougail.a_dyn_family_value_6.a_leadership.a_leader", "value 3")
|
||||
config.value.set("rougail.a_dyn_family_value_6.a_leadership.a_follower", "value 4")
|
||||
print("Mandatories variables without value:")
|
||||
print(config.value.mandatory())
|
||||
walk(config)
|
||||
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
Mandatories variables without value:
|
||||
[<TiramisuOption path="rougail.a_dyn_family_value1.a_leadership.a_leader">, <TiramisuOption path="rougail.a_dyn_family_value1.a_leadership.a_follower">, <TiramisuOption path="rougail.a_dyn_family_value2.a_leadership.a_leader">, <TiramisuOption path="rougail.a_dyn_family_value2.a_leadership.a_follower">]
|
||||
Mandatories variables without value:
|
||||
[]
|
||||
rougail.my_variable (a simple variable): ['value 5', 'value 6']
|
||||
rougail.a_family.my_variable (a simple variable inside the family): my_value
|
||||
rougail.a_dyn_family_value_5.a_leadership.a_leader (a leader variable): value 1
|
||||
rougail.a_dyn_family_value_5.a_leadership.a_follower (a follower variable): value 2
|
||||
rougail.a_dyn_family_value_6.a_leadership.a_leader (a leader variable): value 3
|
||||
rougail.a_dyn_family_value_6.a_leadership.a_follower (a follower variable): value 4
|
||||
167
docs/library/rougailconfig_load_from_cli.rst
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
Load Rougail configuration from Rougail command line informations
|
||||
==================================================================
|
||||
|
||||
There is a lot you can do with the Rougail command line (rougail-cli), but sometimes you need to do a more advanced script.
|
||||
|
||||
Rather than duplicating the configuration, why not load the information from the configuration file, environment variables, or command line options?
|
||||
|
||||
We can loading a combination of source information but always in this order:
|
||||
|
||||
- configuration file
|
||||
- environment variables
|
||||
- commandline options
|
||||
|
||||
.. warning:: specific options reserve for command line (in namespace "cli") are not available in script
|
||||
|
||||
Then let's create an structual file:term:`structure file` :file:`dist/00-base.yml`:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`dist/00-base.yml` file content
|
||||
|
||||
\%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable: my_value_extra # a simple variable
|
||||
...
|
||||
|
||||
Command line configuration file
|
||||
-------------------------------
|
||||
|
||||
Create a command line configuration file :file:`.rougailcli.yml`:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`.rougailcli.yml` file content
|
||||
|
||||
---
|
||||
main_structural_directories: # directories where are place structural file
|
||||
- dist
|
||||
step.output: json # output is not console but json
|
||||
|
||||
Let's execute Rougail command line:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ rougail
|
||||
{
|
||||
"my_variable": "my_value_extra"
|
||||
}
|
||||
|
||||
Then, let's create the :term:`Tiramisu` objects via the following :file:`script.py` script:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail
|
||||
rougail = Rougail()
|
||||
try:
|
||||
config = rougail.run()
|
||||
print(config.value.get())
|
||||
except Exception as err:
|
||||
print(f"ERROR: {err}")
|
||||
exit(1)
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
|
||||
ERROR: option "Directories where structural files are placed" is mandatory but has no value
|
||||
|
||||
As expected, the .rougailcli.yml file is not loaded because it is specific to the command line.
|
||||
|
||||
Let's modifying the script to do this:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.cli.rougailconfig import load
|
||||
load(RougailConfig, yaml_file=".rougailcli.yml")
|
||||
rougail = Rougail()
|
||||
try:
|
||||
config = rougail.run()
|
||||
print(config.value.get())
|
||||
except Exception as err:
|
||||
print(f"ERROR: {err}")
|
||||
exit(1)
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
{<TiramisuOption path="rougail">: {<TiramisuOption path="rougail.my_variable">: 'my_value_extra'}}
|
||||
|
||||
Environment variables
|
||||
---------------------
|
||||
|
||||
If we don't have .rougailcli.yml, it's possible to set option with environment variables, like this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ env ROUGAILCLI_MAIN_STRUCTURAL_DIRECTORIES=dist/ ROUGAILCLI_STEP.OUTPUT=json ROUGAILCLI_MAIN_NAMESPACE=test bin/rougail
|
||||
{
|
||||
"test": {
|
||||
"my_variable": "my_value_extra"
|
||||
}
|
||||
}
|
||||
|
||||
Do the same with a script:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.cli.rougailconfig import load
|
||||
load(RougailConfig, env_prefix="ROUGAILCLI")
|
||||
rougail = Rougail()
|
||||
try:
|
||||
config = rougail.run()
|
||||
print(config.value.get())
|
||||
except Exception as err:
|
||||
print(f"ERROR: {err}")
|
||||
exit(1)
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ env ROUGAILCLI_MAIN_STRUCTURAL_DIRECTORIES=dist/ ROUGAILCLI_STEP.OUTPUT=json ROUGAILCLI_MAIN_NAMESPACE=test python3 script.py
|
||||
{<TiramisuOption path="test">: {<TiramisuOption path="test.my_variable">: 'my_value_extra'}}
|
||||
|
||||
Command line option
|
||||
-------------------
|
||||
|
||||
To reproduce this:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./bin/rougail --main_structural_directories dist/ --step.output json --main_namespace=new_test
|
||||
{
|
||||
"new_test": {
|
||||
"my_variable": "my_value_extra"
|
||||
}
|
||||
}
|
||||
|
||||
Do this script:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.cli.rougailconfig import load
|
||||
load(RougailConfig, commandline=True)
|
||||
rougail = Rougail()
|
||||
try:
|
||||
config = rougail.run()
|
||||
print(config.value.get())
|
||||
except Exception as err:
|
||||
print(f"ERROR: {err}")
|
||||
exit(1)
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py --main_structural_directories dist/ --step.output json --main_namespace=new_test
|
||||
{<TiramisuOption path="new_test">: {<TiramisuOption path="new_test.my_variable">: 'my_value_extra'}
|
||||
112
docs/library/tags.rst
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
Use tag informations
|
||||
====================
|
||||
|
||||
When we set tags for a variable, it will add :term:`Tiramisu` properties and informations.
|
||||
|
||||
We can filter those variables easily with tags.
|
||||
|
||||
First of all, create our structural file:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`dist/00-base.yml` file content
|
||||
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
infra_name: my infra # Name of this infrastructure
|
||||
|
||||
server1: # the first server
|
||||
|
||||
internal_domain:
|
||||
description: Server domaine name
|
||||
type: domainname
|
||||
tags:
|
||||
- internal
|
||||
|
||||
external_domain:
|
||||
description: Domain name to access to this server for Internet
|
||||
type: domainname
|
||||
tags:
|
||||
- external
|
||||
|
||||
server2: # the second server
|
||||
|
||||
address:
|
||||
description: Server domaine name
|
||||
type: domainname
|
||||
tags:
|
||||
- internal
|
||||
...
|
||||
|
||||
Exclude variables with a specific tag
|
||||
-------------------------------------
|
||||
|
||||
To exclude variables with a specific tag is very easy. When the variable has tags, properties with same name are automaticly create.
|
||||
So exclude a tag means exclude variable with a particular property.
|
||||
|
||||
In this example we exclude variable with "internal" tag and display result of "server1" family:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from pprint import pprint
|
||||
|
||||
RougailConfig['main_namespace'] = None
|
||||
RougailConfig['main_structural_directories'] = ['dist']
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
print("without filter:")
|
||||
pprint(config.option("server1").value.get())
|
||||
config.property.add('internal')
|
||||
print("with filter:")
|
||||
pprint(config.option("server1").value.get())
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
without filter:
|
||||
{<TiramisuOption path="server1.external_domain">: None,
|
||||
<TiramisuOption path="server1.internal_domain">: None}
|
||||
with filter:
|
||||
{<TiramisuOption path="server1.external_domain">: None}
|
||||
|
||||
Only variable with a specific tag
|
||||
---------------------------------
|
||||
|
||||
It's more difficult to see only variable with a specific tag.
|
||||
|
||||
We have to walk through the configuration and retrieve variable with the selected tag.
|
||||
Tags are in properties but, are in information too.
|
||||
|
||||
Here is a smal script that walk that the configuration and "print" option with "internal" tag:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
|
||||
RougailConfig['main_structural_directories'] = ['dist']
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
def walk(config):
|
||||
for option in config:
|
||||
if option.isoptiondescription():
|
||||
walk(option)
|
||||
elif "internal" in option.information.get('tags', []):
|
||||
print(option.description())
|
||||
|
||||
if __name__ == '__main__':
|
||||
walk(config)
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
server1.internal_domain (Server domaine name)
|
||||
server2.address (Server domaine name)
|
||||
42
docs/library/upgrade.rst
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
Upgrade dictionnaries to upper version
|
||||
======================================
|
||||
|
||||
All dictionnaries has a format version number.
|
||||
When a new format version is proposed, it is possible to automatically convert the files to the new version.
|
||||
|
||||
We create a term:`structure file` named :file:`dict/01-upgrade.yml` with version 1.0:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
my_variable:
|
||||
multi: true
|
||||
my_dyn_family:
|
||||
type: "dynamic"
|
||||
variable: my_variable
|
||||
a_variable:
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from rougail import RougailUpgrade, RougailConfig
|
||||
>>> RougailConfig['main_structural_directories'] = ['dict']
|
||||
>>> upgrade = RougailUpgrade()
|
||||
>>> upgrade.load_dictionaries('dict_converted')
|
||||
|
||||
The term:`structure file` named :file:`dict_converted/01-upgrade.yml` is in version 1.1:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
version: '1.1'
|
||||
my_variable:
|
||||
multi: true
|
||||
my_dyn_family:
|
||||
type: dynamic
|
||||
a_variable: null
|
||||
dynamic:
|
||||
type: variable
|
||||
variable: my_variable
|
||||
propertyerror: false
|
||||
|
||||
390
docs/library/user_datas.rst
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
Load user datas
|
||||
===============
|
||||
|
||||
User datas are values setup by user for configuration variables.
|
||||
|
||||
There is differents types of user datas for differents sources types.
|
||||
|
||||
We can cumulate user datas loader.
|
||||
|
||||
For this section, we will use :file:`dict/00-base.yml` a structure file:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable: my value # My first variable
|
||||
|
||||
my_boolean_variable: true # My boolean variable
|
||||
|
||||
my_integer_variable: 1 # My integer variable
|
||||
|
||||
my_secret_variable:
|
||||
description: My secret variable
|
||||
type: secret
|
||||
|
||||
my_hidden_variable:
|
||||
description: My hidden variable
|
||||
hidden: true
|
||||
...
|
||||
|
||||
Here is the first script which is load this file:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
print(config.value.get())
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
{<TiramisuOption path="my_variable">: 'my value', <TiramisuOption path="my_boolean_variable">: True, <TiramisuOption path="my_integer_variable">: 1, <TiramisuOption path="my_secret_variable">: None}
|
||||
|
||||
YAML
|
||||
----
|
||||
|
||||
We want to load this YAML file with value define by user:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
my_variable: a new value
|
||||
|
||||
my_boolean_variable: false
|
||||
|
||||
my_integer_variable: 10
|
||||
|
||||
my_secret_variable: MyVeryStrongPassword
|
||||
|
||||
Here is the script which is load user data from the YAML file:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.user_data_yaml import RougailUserDataYaml
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.user_data"] = ["yaml"]
|
||||
RougailConfig["yaml.filename"] = ["dist.yml"]
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
user_datas = RougailUserDataYaml(config).run()
|
||||
rougail.user_datas(user_datas)
|
||||
print(config.value.get())
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: 'MyVeryStrongPassword'}
|
||||
|
||||
Set a secret in clear text file is not always a good idea.
|
||||
This is why the `yaml.file_with_secrets` parameter allows you to define whether files define in `yaml.filename` can contain a secret and which one:
|
||||
|
||||
- all: all file can contains secret
|
||||
- first: only the first file can contains secret
|
||||
- last: only the last file can contains secret
|
||||
- none: no file can contains secret
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.user_data_yaml import RougailUserDataYaml
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.user_data"] = ["yaml"]
|
||||
RougailConfig["yaml.filename"] = ["dist.yml"]
|
||||
RougailConfig["yaml.file_with_secrets"] = "none"
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
user_datas = RougailUserDataYaml(
|
||||
config,
|
||||
).run()
|
||||
rougail.user_datas(user_datas)
|
||||
print(config.value.get())
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python3 script.py
|
||||
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: None}
|
||||
|
||||
Environment variables
|
||||
---------------------
|
||||
|
||||
We can define use data from environment variables. The environment name is a "prefix" (ROUGAIL by default) with "_" and variable name in uppercase format.
|
||||
|
||||
For example:
|
||||
|
||||
- `my_variable` has `ROUGAIL_MY_VARIABLE` as a environment variable name
|
||||
- `my_family.my_variable` has `ROUGAIL_MY_FAMILY.MY_VARIABLE` as a environment variable name
|
||||
|
||||
Some shell doesn't allow dot in environment file. In this case use the command "env".
|
||||
|
||||
For example: `env ROUGAIL_MY_FAMILY.MY_VARIABLE="value" ./script.py`.
|
||||
|
||||
Here is the script:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.user_data_environment import RougailUserDataEnvironment
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.user_data"] = ["environment"]
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
user_datas = RougailUserDataEnvironment(config).run()
|
||||
rougail.user_datas(user_datas)
|
||||
print(config.value.get())
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
env ROUGAIL_MY_VARIABLE="a new value" ROUGAIL_MY_BOOLEAN_VARIABLE="False" ROUGAIL_MY_INTEGER_VARIABLE=10 ROUGAIL_MY_SECRET_VARIABLE="MyVeryStrongPassword" python script.py
|
||||
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: 'MyVeryStrongPassword'}
|
||||
|
||||
We can redefine the prefix with `environment.default_environment_name` (prefix is always uppercase characters):
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.user_data_environment import RougailUserDataEnvironment
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.user_data"] = ["environment"]
|
||||
RougailConfig["environment.default_environment_name"] = "EX"
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
user_datas = RougailUserDataEnvironment(config).run()
|
||||
rougail.user_datas(user_datas)
|
||||
print(config.value.get())
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
env EX_MY_VARIABLE="a new value" EX_MY_BOOLEAN_VARIABLE="False" EX_MY_INTEGER_VARIABLE=10 EX_MY_SECRET_VARIABLE="MyVeryStrongPassword" python script.py
|
||||
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: 'MyVeryStrongPassword'}
|
||||
|
||||
If you define a `main_namespace` or `extra_namespaces`, the `environment.default_environment_name` is automaticly define with the name of the namespace in uppercase. And the separator is no more "_" but ".":
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.user_data_environment import RougailUserDataEnvironment
|
||||
|
||||
RougailConfig["main_namespace"] = "main"
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.user_data"] = ["environment"]
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
user_datas = RougailUserDataEnvironment(config).run()
|
||||
rougail.user_datas(user_datas)
|
||||
print(config.value.get())
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
env MAIN.MY_VARIABLE="a new value" MAIN.MY_BOOLEAN_VARIABLE="False" MAIN.MY_INTEGER_VARIABLE=10 MAIN.MY_SECRET_VARIABLE="MyVeryStrongPassword" python script.py
|
||||
{<TiramisuOption path="main">: {<TiramisuOption path="main.my_variable">: 'a new value', <TiramisuOption path="main.my_boolean_variable">: False, <TiramisuOption path="main.my_integer_variable">: 10, <TiramisuOption path="main.my_secret_variable">: 'MyVeryStrongPassword'}}
|
||||
|
||||
Set a secret in clear variable environment is not always a good idea.
|
||||
This is why the `environment.with_secrets` parameter allows you to reject secret from environment variable:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.user_data_environment import RougailUserDataEnvironment
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.user_data"] = ["environment"]
|
||||
RougailConfig["environment.with_secrets"] = False
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
user_datas = RougailUserDataEnvironment(config).run()
|
||||
rougail.user_datas(user_datas)
|
||||
print(config.value.get())
|
||||
|
||||
Let's execute `script.py`:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
env ROUGAIL_MY_VARIABLE="a new value" ROUGAIL_MY_BOOLEAN_VARIABLE="False" ROUGAIL_MY_INTEGER_VARIABLE=10 ROUGAIL_MY_SECRET_VARIABLE="MyVeryStrongPassword" python script.py
|
||||
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: None}
|
||||
|
||||
Comand line parser user data
|
||||
----------------------------
|
||||
|
||||
Value can be define directly with command line arguments:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.user_data_commandline import RougailUserDataCommandline
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.user_data"] = ["commandline"]
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
user_datas = RougailUserDataCommandline(
|
||||
config,
|
||||
).run()
|
||||
rougail.user_datas(user_datas)
|
||||
print(config.value.get())
|
||||
|
||||
Let's execute `script.py` to display help:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python script.py -h
|
||||
usage: script.py [-h] --my_variable [MY_VARIABLE] --my_boolean_variable --no-my_boolean_variable --my_integer_variable [MY_INTEGER_VARIABLE] --my_secret_variable MY_SECRET_VARIABLE
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--my_variable [MY_VARIABLE]
|
||||
my_variable (My first variable) (default: my value)
|
||||
--my_boolean_variable
|
||||
my_boolean_variable (My boolean variable) (default: True)
|
||||
--no-my_boolean_variable
|
||||
--my_integer_variable [MY_INTEGER_VARIABLE]
|
||||
my_integer_variable (My integer variable) (default: 1)
|
||||
--my_secret_variable MY_SECRET_VARIABLE
|
||||
my_secret_variable (My secret variable)
|
||||
{<TiramisuOption path="my_variable">: 'my value', <TiramisuOption path="my_boolean_variable">: True, <TiramisuOption path="my_integer_variable">: 1, <TiramisuOption path="my_secret_variable">: None}
|
||||
|
||||
|
||||
And now with modified value:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ python script.py --my_variable "a new value" --no-my_boolean_variable --my_integer_variable 10 --my_secret_variable MyVeryStrongPassword
|
||||
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: 'MyVeryStrongPassword'}
|
||||
|
||||
Boolean variable has a special behavour. To set False you need to add --no-VARIABLE, to set True you need to add --VARIABLE parameter.
|
||||
|
||||
.. ansible,bitwarden,questionary
|
||||
|
||||
Combine user datas
|
||||
------------------
|
||||
|
||||
You can combine user datas, for example if you want to load datas from environment and/or command line argument:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.user_data_environment import RougailUserDataEnvironment
|
||||
from rougail.user_data_commandline import RougailUserDataCommandline
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.user_data"] = ["environment", "commandline"]
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
user_datas = []
|
||||
user_datas.extend(RougailUserDataEnvironment(
|
||||
config,
|
||||
).run())
|
||||
user_datas.extend(RougailUserDataCommandline(
|
||||
config,
|
||||
).run())
|
||||
rougail.user_datas(user_datas)
|
||||
print(config.value.get())
|
||||
|
||||
Let's execute `script.py` with environment variable and commandline arguments:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ env ROUGAIL_MY_VARIABLE="a new value" ROUGAIL_MY_BOOLEAN_VARIABLE="False" python script.py --my_integer_variable 10 --my_secret_variable MyVeryStrongPassword
|
||||
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: 'MyVeryStrongPassword'}
|
||||
|
||||
If the value of a variable is define with an environment variable and commandline argument, the value is the value of the last user data define:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ env ROUGAIL_MY_VARIABLE="not a new" python script.py --my_variable "a new value" --no-my_boolean_variable --my_integer_variable 10 --my_secret_variable MyVeryStrongPassword
|
||||
{<TiramisuOption path="my_variable">: 'a new value', <TiramisuOption path="my_boolean_variable">: False, <TiramisuOption path="my_integer_variable">: 10, <TiramisuOption path="my_secret_variable">: 'MyVeryStrongPassword'}
|
||||
|
||||
Manage errors and warnings
|
||||
--------------------------
|
||||
|
||||
Recreate a script with environnement variable support which is display the return of user_datas function:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: the :file:`script.py` file content
|
||||
|
||||
from rougail import Rougail, RougailConfig
|
||||
from rougail.user_data_environment import RougailUserDataEnvironment
|
||||
|
||||
RougailConfig["main_namespace"] = None
|
||||
RougailConfig["main_structural_directories"] = ["dist/"]
|
||||
RougailConfig["step.user_data"] = ["environment"]
|
||||
RougailConfig["environment.with_secrets"] = False
|
||||
rougail = Rougail()
|
||||
config = rougail.run()
|
||||
|
||||
user_datas = RougailUserDataEnvironment(
|
||||
config,
|
||||
).run()
|
||||
print(rougail.user_datas(user_datas))
|
||||
|
||||
Try to load the value an unknown variable:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ env ROUGAIL_UNKNOWN_VARIABLE="a value" python script.py
|
||||
{'errors': [], 'warnings': ['variable or family "unknown_variable" does not exist, it will be ignored when loading from environment variable']}
|
||||
|
||||
As you can see, a warnings is return.
|
||||
|
||||
Try to load the value of an hidden variable:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ env ROUGAIL_MY_HIDDEN_VARIABLE="a value" python script.py
|
||||
{'errors': [], 'warnings': ['variable "my_hidden_variable" (My hidden variable) is hidden, it will be ignored when loading from environment variable']}
|
||||
|
||||
Finally if a try to change the value of a secret, which is not allowed:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ env ROUGAIL_MY_SECRET_VARIABLE="MyVeryStrongPassword" python script.py
|
||||
{'errors': ['the variable "my_secret_variable" contains secrets and should not be defined in environment variable'], 'warnings': []}
|
||||
|
||||
An error is generated.
|
||||
46
docs/naming_convention.rst
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
.. _namingconvention:
|
||||
|
||||
File naming convention
|
||||
==========================
|
||||
|
||||
The structure files in a given folder
|
||||
---------------------------------------
|
||||
|
||||
For the sake of clarity, we put the structure definitions in separate files.
|
||||
It's a good way to organize your rougail structures this way,
|
||||
in the real world we need separate files for different topics.
|
||||
|
||||
For example some files like this:
|
||||
|
||||
A file named :file:`firefox/00-proxy.yml` structure file and file named :file:`firefox/10-manual.yml`
|
||||
|
||||
::
|
||||
|
||||
.
|
||||
└── firefox
|
||||
├── 00-proxy.yml
|
||||
└── 10-manual.yml
|
||||
|
||||
.. note:: We of course could have put everything in one file.
|
||||
Again, it is better to separate the structures in different files
|
||||
for reasons of ordering and clarity.
|
||||
|
||||
Ordering your structure files
|
||||
--------------------------------
|
||||
|
||||
The order in which files are loaded is important in Rougail.
|
||||
In a given folder, files are loaded in alphabetical order.
|
||||
|
||||
Furthermore, for better readability of the order in which files are
|
||||
loaded into a folder, we have adopted a convention.
|
||||
To facilitate classification, we have defined a standard notation for structure file names:
|
||||
|
||||
::
|
||||
|
||||
XX-<name>.yml
|
||||
|
||||
Where `XX` is a two digits integer followed by an hyphen, and `<name>` is a name that describes
|
||||
the structure that is in this file. We advise you to adopt this convention as well.
|
||||
|
||||
Moreover, it is preferable to only use lowercase ASCII letters, numbers and the `"_"` (undescore) character.
|
||||
The snake case typographic convention is therefore used.
|
||||
25
docs/readme.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
Building the doc locally
|
||||
============================
|
||||
|
||||
install
|
||||
---------
|
||||
|
||||
First, install a python virtual environment::
|
||||
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
Then install the sphinx libraries::
|
||||
|
||||
./.venv/bin/pip3 install sphinx
|
||||
./.venv/bin/pip3 install sphinx_rtd_theme
|
||||
./.venv/bin/pip3 install sphinx_lesson
|
||||
|
||||
|
||||
The generatef html output is located in the `docs/build/html` subfolder,
|
||||
you can modify the target or the output type in the :file:`docs/Makefile`.
|
||||
|
||||
scraps
|
||||
---------
|
||||
|
||||
`variable <https://en.wikipedia.org/wiki/Variable_(computer_science)>`_
|
||||
50
docs/relative_path.rst
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
:orphan:
|
||||
|
||||
on a un path relatif, `_` ou `__`
|
||||
|
||||
|
||||
https://forge.cloud.silique.fr/stove/rougail-tutorials/compare/v1.1_091~1..v1.1_091
|
||||
|
||||
|
||||
::
|
||||
|
||||
manual:
|
||||
|
||||
use_for_https: true # Also use this proxy for HTTPS
|
||||
|
||||
"{{ identifier }}_proxy":
|
||||
description: "{{ identifier }} Proxy"
|
||||
dynamic:
|
||||
- HTTPS
|
||||
- SOCKS
|
||||
hidden:
|
||||
jinja: |
|
||||
{% if my_identifier == 'HTTPS' and _.use_for_https %}
|
||||
HTTPS is same has HTTP
|
||||
{% endif %}
|
||||
params:
|
||||
my_identifier:
|
||||
type: identifier
|
||||
description: |
|
||||
in HTTPS case if "manual.use_for_https" is set to True
|
||||
|
||||
address:
|
||||
description: "{{ identifier }} address"
|
||||
default:
|
||||
variable: __.http_proxy.address
|
||||
|
||||
port:
|
||||
description: "{{ identifier }} port"
|
||||
default:
|
||||
variable: __.http_proxy.port
|
||||
|
||||
version:
|
||||
description: SOCKS host version used by proxy
|
||||
choices:
|
||||
- v4
|
||||
- v5
|
||||
default: v5
|
||||
disabled:
|
||||
type: identifier
|
||||
when: 'HTTPS'
|
||||
|
||||
77
docs/rw_ro_modes.rst
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
:orphan:
|
||||
|
||||
Read-write or read-only modes
|
||||
==================================
|
||||
|
||||
The read-write mode
|
||||
--------------------
|
||||
|
||||
When you are in the design phase, your are designing the structure file or
|
||||
setting values is some user data files, you have the role of :term:`integrator`
|
||||
or :term:`operator`. Then you need to have access to all the datas, even
|
||||
those which are :term:`hidden` or :term:`disabled`\ .
|
||||
|
||||
In this phase, the configuration shall be in `RW` mode.
|
||||
|
||||
|
||||
.. glossary::
|
||||
|
||||
read-write
|
||||
|
||||
In the read-write mode (RW mode), all settings are accessible and can
|
||||
be modified. For example, a variable which has the `frozen` property
|
||||
can be modified. A `hidden` or `disabled` variable is accessible.
|
||||
|
||||
We are in the rôle of an :term:`integrator` or an :term:`operator`.
|
||||
We are therefore in a situation where we are **using** this configuration.
|
||||
|
||||
The read-only mode
|
||||
--------------------
|
||||
|
||||
Once the configuration has beed designed, it is used.
|
||||
The situation is different. The configuration behaves as a system.
|
||||
|
||||
.. glossary::
|
||||
|
||||
read-only
|
||||
|
||||
In the read-only mode (RO mode), the configuration cannot be modified.
|
||||
We are **using** a configuration.
|
||||
|
||||
In the usage mode, we are therefore in a situation where the configuration
|
||||
cannot be changed. The configuration's data are immutable.
|
||||
|
||||
|
||||
RO or RW mode?
|
||||
---------------
|
||||
|
||||
Here is an image which summarizes these explanations:
|
||||
|
||||
.. image:: images/read_write.png
|
||||
|
||||
|
||||
How to enable
|
||||
|
||||
By default in `rougail-cli`, the `RO` mode is activated.
|
||||
|
||||
If you need to enable the `RW` mode, there is an `rougail-cli` option:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
env ROUGAIL_MANUAL.USE_FOR_HTTPS=true rougail -m structfile/proxy2.yml -u yaml environment --yaml.filename userdata/proxy.yml -o json --json.read_write
|
||||
|
||||
The output is:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"manual": {
|
||||
"http_proxy": {
|
||||
"address": "toto.fr",
|
||||
"port": "8888"
|
||||
},
|
||||
"use_for_https": true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
101
docs/structfile.rst
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
The structure files
|
||||
=====================
|
||||
|
||||
Definition
|
||||
------------
|
||||
|
||||
.. glossary::
|
||||
|
||||
structure file
|
||||
|
||||
A structure file in the Rougail meaning is a YAML file that describes variables
|
||||
and their dependencies.
|
||||
There can be a lot of structure files located in many different folders.
|
||||
|
||||
Rougail reads all the structure files and loads them into a single object
|
||||
that represents the whole :term:`context`.
|
||||
|
||||
|
||||
The header of a structure file
|
||||
-----------------------------------
|
||||
|
||||
A basic structure file is composed of:
|
||||
|
||||
- a YAML 1.2 standard header
|
||||
- a version attribute
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_000/firefox/00-proxy.yml
|
||||
:language: yaml
|
||||
:caption: An empty Rougail structure file.
|
||||
:name: RougailStructureFirstVariableDescription
|
||||
|
||||
..
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
...
|
||||
|
||||
The structure file processing
|
||||
----------------------------------
|
||||
|
||||
The structured files contain information that will be used by the consistency
|
||||
handling system for structure validation.
|
||||
|
||||
.. figure:: images/schema.png
|
||||
:alt: The Rougail process
|
||||
:align: center
|
||||
|
||||
The Rougail process from structure files to Tiramisu valition handler object
|
||||
|
||||
The structured data
|
||||
---------------------
|
||||
|
||||
.. glossary::
|
||||
|
||||
structured data
|
||||
|
||||
We sometimes call "structured datas" the datas that are defined in the structure files,
|
||||
as opposed to :term:`user datas <user datas>`\ .
|
||||
For example when a value of a variable is defined in the structured datas, that is
|
||||
in a structured file, the assigned value's status is that the variable's value is a default value.
|
||||
If the assigned value of the variable is defined in a user data file,
|
||||
it is an user assigned value.
|
||||
|
||||
We'll see later on some examples of default values and user assigned values.
|
||||
|
||||
The main advantage of all of this that declaring variables and writing consistency is as simple
|
||||
as writing YAML. With Rougail it is not necessary to write :term:`Tiramisu` code any more.
|
||||
It simplifies a lot of things.
|
||||
|
||||
And rather than writing :term:`Tiramisu` code, we can declare variables and describe the relationships between variables in a declarative style (that is, in a YAML file).
|
||||
|
||||
Once the structure files are loaded by Rougail, the Tiramisu configuration management tool can check the consistency of the variables between them.
|
||||
|
||||
What contains exactly a :term:`structure file`?
|
||||
-------------------------------------------------
|
||||
|
||||
A :term:`structure file` is a YAML file whose structure is described in this documentation page.
|
||||
|
||||
A structure file contains a set of variables loaded into :term:`Tiramisu`, usable in your application, for example in a :term:`template`
|
||||
to generate configuration files.
|
||||
|
||||
:term:`Families` and :term:`variables` can be defined in several structure files. These structure files are then aggregated.
|
||||
|
||||
If you want to see the contents of a structure file, you can have a look at the :ref:`tutorial with a real world sample. <tutorial>`
|
||||
|
||||
The default namespace
|
||||
-------------------------
|
||||
|
||||
The families and variables contained in these structure files are ordered, by default, in the `rougail` namespace. It is possible to change the name of this namespace with the :term:`variable namespace <variable_namespace>` parameter of the :doc:`configuration <configuration>`.
|
||||
|
||||
This namespace is a bit special, it can access variables in another namespace.
|
||||
|
||||
The extra structure files
|
||||
---------------------------
|
||||
|
||||
An extra is a different namespace. The idea is to be able to classify the variables by theme.
|
||||
|
||||
Extra namespaces must be declared :doc:`when configuring Rougail <configuration>`.
|
||||
|
||||
In this namespace we cannot access variables from another `extra` namespace.
|
||||
On the other hand, it is possible to access the variable of the default namespace.
|
||||
34
docs/tags.rst
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
The variable tags
|
||||
==================
|
||||
|
||||
A tag allows you to specialize a variable.
|
||||
|
||||
A tag can have several functions:
|
||||
|
||||
- Additional information in the documentation
|
||||
- Variable exclusion
|
||||
- Variable selection
|
||||
- ...
|
||||
|
||||
Tags are only available for variable.
|
||||
|
||||
Here is an example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
internal_domain:
|
||||
description: Server domaine name
|
||||
type: domainname
|
||||
tags:
|
||||
- internal
|
||||
|
||||
external_domain:
|
||||
description: Domain name to access to this server for Internet
|
||||
type: domainname
|
||||
tags:
|
||||
- external
|
||||
...
|
||||
383
docs/tutorial/boolean.rst
Normal file
|
|
@ -0,0 +1,383 @@
|
|||
The `hidden` property
|
||||
=======================
|
||||
|
||||
.. objectives:: Objectives
|
||||
|
||||
In this section we will:
|
||||
|
||||
- use a new family or variable's property: the `hidden` property
|
||||
- create a variable with a new type, the `boolean` type
|
||||
- reuse a value of a variable for another variable
|
||||
(in this use case we will reuse the HTTP configuration variables values for the configuration of the HTTPS).
|
||||
|
||||
.. prerequisites:: Reminder
|
||||
|
||||
Let's summarize the configuration up to here
|
||||
|
||||
Whe have the `proxy_mode` configuration here:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/firefox/00-proxy.yml
|
||||
:language: yaml
|
||||
:caption: firefox/00-proxy.yml
|
||||
|
||||
..
|
||||
---
|
||||
proxy_mode:
|
||||
description: Configure Proxy Access to the Internet
|
||||
choices:
|
||||
- No proxy
|
||||
- Auto-detect proxy settings for this network
|
||||
- Use system proxy settings
|
||||
- Manual proxy configuration
|
||||
- Automatic proxy configuration URL
|
||||
default: No proxy
|
||||
|
||||
With the `manual` family that is used in case of the `"Manual proxy configuration"` value of the `proxy_mode` variable has been chosen:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/firefox/10-manual.yml
|
||||
:language: yaml
|
||||
:caption: firefox/10-manual.yml
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
description: Manual proxy configuration
|
||||
disabled:
|
||||
variable: proxy_mode
|
||||
when_not: 'Manual proxy configuration'
|
||||
|
||||
http_proxy:
|
||||
description: HTTP Proxy
|
||||
|
||||
address:
|
||||
description: HTTP address
|
||||
type: domainname
|
||||
params:
|
||||
allow_ip: true
|
||||
|
||||
port:
|
||||
description: HTTP Port
|
||||
type: port
|
||||
default: 8080
|
||||
|
||||
OK then. Let's continue our proxy's configuration.
|
||||
|
||||
.. type-along:: The HTTPS mode
|
||||
|
||||
Now we will focus on configuring the HTTPS mode in case of `"Manual proxy configuration"` value has been chosen.
|
||||
|
||||
.. image:: images/firefox_manual_https.png
|
||||
|
||||
We are going to define two other variables for the HTTPS use only:
|
||||
|
||||
.. confval:: https_proxy.address
|
||||
:type: `domainname`
|
||||
|
||||
This is an address setting for the manual HTTPS configuration
|
||||
|
||||
.. confval:: https_proxy.port
|
||||
:type: `port`
|
||||
|
||||
This is a port setting for the manual HTTPS configuration
|
||||
|
||||
- There are now two very similar variables, a `manual.http_proxy.address` variable and a `manual.https_proxy.address` variable
|
||||
- In the same way, we have a `manual.http_proxy.port` variable and a `manual.https_proxy.port` variable.
|
||||
|
||||
The context
|
||||
--------------
|
||||
|
||||
Let's introduce a new Rougail concept here:
|
||||
|
||||
.. glossary::
|
||||
|
||||
context
|
||||
|
||||
A :term:`configuration` is highly statefull and can change at any moment.
|
||||
Sometimes somes minor changes in the :term:`user datas` may involve chain reactions
|
||||
in the whole :term:`configuration`.
|
||||
The **context** is the state of the user datas at one moment. The set of the values of the variables
|
||||
at a given moment.
|
||||
This term also refers
|
||||
to the ability of a system to handle the *statefull* state of a configuration.
|
||||
It expresses the transition between one situation to another situation,
|
||||
that is, the deeply **statefull** aspects of a data set.
|
||||
|
||||
|
||||
.. type-along:: A new variable which has the `boolean` type
|
||||
|
||||
The best way to reproduce the `"Also use this HTTP proxy variables for HTTPS"` checkbox in the firefox interface
|
||||
is to add a boolean variable in our structure. A boolean variable can reproduce this binary choice option.
|
||||
|
||||
Do we want to reuse, for the HTTPS mode, the same configuration as for the HTTP mode?
|
||||
Well, it depends on the :term:`context`.
|
||||
|
||||
Let's create a new variable, named `use_for_https` here:
|
||||
|
||||
.. confval:: use_for_https
|
||||
:type: `boolean`
|
||||
:default: `true`
|
||||
|
||||
This is a setting that enables to reuse or not the HTTP proxy configuration for HTTPS
|
||||
|
||||
.. questions:: Question: how does it work?
|
||||
|
||||
How will this variable drive the reuse of HTTP data to HTTPS data?
|
||||
|
||||
Its description in the structure file gives us this:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/firefox/20-manual.yml
|
||||
:language: yaml
|
||||
:caption: firefox/20-manual.yml
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
|
||||
use_for_https:
|
||||
description: Also use this proxy for HTTPS
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
.. prerequisites:: Reminder
|
||||
|
||||
So now we have three new variables in the `manual` family (with its `https_proxy` subfamily) which express the manual mode of the http proxy configuration.
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_032/firefox/20-manual.yml
|
||||
:language: yaml
|
||||
:caption: `firefox/20-manual.yml`
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
|
||||
use_for_https:
|
||||
description: Also use this proxy for HTTPS
|
||||
default: true
|
||||
|
||||
https_proxy:
|
||||
description: HTTPS Proxy
|
||||
|
||||
address:
|
||||
description: HTTPS address
|
||||
type: domainname
|
||||
params:
|
||||
allow_ip: true
|
||||
|
||||
port:
|
||||
description: HTTPS Port
|
||||
type: port
|
||||
default: 8080
|
||||
|
||||
And with this :confval:`use_for_https` boolean variable, there are two possibilities, and only two:
|
||||
|
||||
- The http proxy's configuration will be reused for the https proxy's configuration
|
||||
- The http proxy's will not be reused for the https proxy's configuration
|
||||
|
||||
.. questions:: Question: shall we use the `disabled` property here?
|
||||
|
||||
Is it relevant to use the :term:`disabled property <disabled>` here?
|
||||
|
||||
**answer**: No! Because we *need* to use these variables at any :term:`context` of the proxy's manual configuration use case,
|
||||
we simply have to point their values in one direction or another depending on this or that context.
|
||||
|
||||
It is absolutely not a question of deactivating them. The `manual.https_proxy.address`
|
||||
and the `manual.http_proxy.port` variables shall not be disabled (deactivated) in the manual mode.
|
||||
|
||||
We have to create a new property here.
|
||||
|
||||
The `hidden` property
|
||||
----------------------
|
||||
|
||||
.. glossary::
|
||||
|
||||
hidden
|
||||
|
||||
A variable or family's property is hidden if its value **shall not be seen** in a given :term:`context`.
|
||||
Anyway, these variables can be used again if the context evolves. This is the main difference
|
||||
between the `hidden` and the `disabled` properties.
|
||||
|
||||
Now we can set a `hidden` property to the `https_proxy` family:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_033/firefox/20-manual.yml
|
||||
:language: yaml
|
||||
:caption: The `https_proxy` subfamily with the `hidden` property
|
||||
:name: HiddenPropertySubfamily
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
|
||||
use_for_https:
|
||||
description: Also use this proxy for HTTPS
|
||||
default: true
|
||||
|
||||
https_proxy:
|
||||
description: HTTPS Proxy
|
||||
hidden: true
|
||||
|
||||
address:
|
||||
description: HTTPS address
|
||||
type: domainname
|
||||
params:
|
||||
allow_ip: true
|
||||
|
||||
port:
|
||||
description: HTTPS Port
|
||||
type: port
|
||||
default: 8080
|
||||
|
||||
The whole `https_proxy` family has been set to `hidden` here.
|
||||
|
||||
.. warning:: A `hidden` variable with the `mandatory` parameter set still shall have a defined value,
|
||||
even if it's hidden.
|
||||
|
||||
If we choose the manual proxy configuration mode,
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_033/config/03/config.yaml
|
||||
:language: yaml
|
||||
:caption: The Manual proxy configuration value is set
|
||||
|
||||
..
|
||||
---
|
||||
proxy_mode: Manual proxy configuration
|
||||
|
||||
|
||||
Note that in this context, if we don't set a value to the `manual.http_proxy.address` mandatory variable, even if it is `hidden`,
|
||||
Rougail will raise an error:
|
||||
|
||||
.. todo:: Ce raw html là est sur manual.http_proxy.address et pas manual.https_proxy.address
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_033/config/03/output_ro.html
|
||||
:class: error-box
|
||||
|
||||
..
|
||||
<pre>🛑 ERRORS
|
||||
<span style="color: #ff0000">┣━━ </span>The following variables are mandatory but have no value:
|
||||
<span style="color: #ff0000">┗━━ </span> - manual.http_proxy.address (HTTP address)
|
||||
</pre>
|
||||
|
||||
A contextual hidden family driven by a boolean variable
|
||||
-----------------------------------------------------------
|
||||
|
||||
What we want is having this `hidden` property assigned dynamically depending on the `use_for_https` `true` or `false` value.
|
||||
|
||||
Here is how to achieve this:
|
||||
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_034/firefox/20-manual.yml
|
||||
:language: yaml
|
||||
:caption: The hidden property now depends on the value of `manual.use_for_https`
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
|
||||
use_for_https:
|
||||
description: Also use this proxy for HTTPS
|
||||
default: true
|
||||
|
||||
https_proxy:
|
||||
description: HTTPS Proxy
|
||||
hidden:
|
||||
variable: manual.use_for_https
|
||||
|
||||
address:
|
||||
description: HTTPS address
|
||||
type: domainname
|
||||
params:
|
||||
allow_ip: true
|
||||
|
||||
port:
|
||||
description: HTTPS Port
|
||||
type: port
|
||||
default: 8080
|
||||
|
||||
Yes, it is possible to add a `variable` parameter to the `hidden` attribute.
|
||||
|
||||
A contextualized default value
|
||||
---------------------------------
|
||||
|
||||
|
||||
.. discussion:: A contextualized default value
|
||||
|
||||
A contextualized default value is a default value (of a variable) that is driven by another variable.
|
||||
This variable type and its parameters type are copied in the default value's target variable.
|
||||
|
||||
There is something left in the https configuration mode of the proxy:
|
||||
|
||||
- if the use of the proxy variables for https are the same of the proxy variables for http,
|
||||
that is, if `use_for_https` is true, the https configuration variables are hidden, that's OK.
|
||||
- if the use of the proxy variables for https **are not** the same of the proxy variables for http,
|
||||
we would like to set their default values to the http proxy variables values.
|
||||
|
||||
By now, the default settings are set like this:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
port:
|
||||
description: HTTPS Port
|
||||
type: port
|
||||
default: 8080
|
||||
|
||||
The dynamic setting of a default can be achieved in this way:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_035/firefox/20-manual.yml
|
||||
:language: yaml
|
||||
:caption: A default variable's value dynamically set
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
|
||||
use_for_https:
|
||||
description: Also use this proxy for HTTPS
|
||||
default: true
|
||||
|
||||
https_proxy:
|
||||
description: HTTPS Proxy
|
||||
hidden:
|
||||
variable: manual.use_for_https
|
||||
|
||||
address:
|
||||
description: HTTPS address
|
||||
type: domainname
|
||||
params:
|
||||
allow_ip: true
|
||||
default:
|
||||
variable: manual.http_proxy.address
|
||||
|
||||
port:
|
||||
description: HTTPS Port
|
||||
type: port
|
||||
default:
|
||||
variable: manual.http_proxy.port
|
||||
|
||||
The default value is a pointer to another variable's value.
|
||||
Here, the defaut value of `manual.https_proxy.address` points to `manual.http_proxy.address`.
|
||||
|
||||
This is the same thing for the default value of the `manual.https_proxy.port` variable,
|
||||
which points to the `manual.http_proxy.port` value.
|
||||
|
||||
We also say that the default value is *calculated*.
|
||||
|
||||
.. glossary::
|
||||
|
||||
calculated
|
||||
|
||||
We say that a variable's value or a default variable's value is calculated
|
||||
when there is a pointer which refers to another variable's value
|
||||
or if there is some :term:`jinja` code or a python function that calculates it.
|
||||
|
||||
.. keypoints:: Key points
|
||||
|
||||
**Keywords**
|
||||
|
||||
- The :term:`hidden` property set to a family
|
||||
- The fact that a property can be set dynamically
|
||||
- The conditional dependency of a `hidden` property that depends on a `boolean` variable.
|
||||
- A calculated default value
|
||||
|
||||
**Progress**
|
||||
|
||||
We have arrived at the end of the proxy's manual configuration's section.
|
||||
311
docs/tutorial/calculation.rst
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
Calculation with a jinja type
|
||||
===============================
|
||||
|
||||
.. objectives:: Objectives
|
||||
|
||||
In this section we will learn how to create new ways of calculation.
|
||||
|
||||
Up to now, our only way of dynamically (that is, during the runtime) calculating
|
||||
a value is to point on another variable's value. But this is not the only way.
|
||||
|
||||
.. todo:: on peut aussi faire un exercice de ce genre pour la propriété mandatory
|
||||
|
||||
|
||||
A jinja calculated variable's hidden property
|
||||
------------------------------------------------
|
||||
|
||||
We can hide or disable some variables or families with other techniques than
|
||||
pointing on a variable's value.
|
||||
|
||||
Let's reason on the previous HTTPS proxy configuration's manual mode:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
manual:
|
||||
|
||||
use_for_https:
|
||||
description: Also use this proxy for HTTPS
|
||||
default: true
|
||||
|
||||
https_proxy:
|
||||
type: family
|
||||
description: HTTPS Proxy
|
||||
hidden:
|
||||
variable: manual.use_for_https
|
||||
|
||||
|
||||
This is extracted from the proxy's manual configuration we discussed before.
|
||||
|
||||
We see here that there is an `https_proxy` family that is going to be hidden
|
||||
depending on the value of another variable:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
https_proxy:
|
||||
type: family
|
||||
hidden:
|
||||
variable: manual.use_for_https
|
||||
|
||||
Now we could write it like that:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
manual:
|
||||
|
||||
use_for_https:
|
||||
description: Also use this proxy for HTTPS
|
||||
default: true
|
||||
|
||||
https_proxy:
|
||||
description: HTTPS Proxy
|
||||
type: family
|
||||
hidden:
|
||||
type: jinja
|
||||
jinja: |
|
||||
{% if rougail.manual.use_for_https %}
|
||||
the HTTPS Proxy family is hidden
|
||||
{% endif %}
|
||||
|
||||
Yes, it's done in a more complicated (but more powerful) way.
|
||||
Let's explain this a little:
|
||||
|
||||
We have replaced this simple hidden property declaration:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
hidden:
|
||||
variable: manual.use_for_https
|
||||
|
||||
by this (more complicated) hidden property declaration:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
hidden:
|
||||
type: jinja
|
||||
jinja: |
|
||||
{% if rougail.manual.use_for_https %}
|
||||
the HTTPS Proxy family is hidden
|
||||
{% endif %}
|
||||
|
||||
The fact is that it has same result, but here we have more possibilities.
|
||||
The hidden process is done by a calculation.
|
||||
|
||||
Another jinja calculation type sample
|
||||
---------------------------------------
|
||||
|
||||
We can now hide or disable some variables or families with other techniques than
|
||||
pointing on a variable's value.
|
||||
|
||||
Let's reason upon the proxy's manual configuration we discussed before.
|
||||
We have the :file:`dict/02-proxy_manual.yml` structure file:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`structfile/02-proxy_manual.yml` file
|
||||
|
||||
---
|
||||
version: '1.1'
|
||||
manual:
|
||||
description: Manual proxy configuration
|
||||
type: family
|
||||
disabled:
|
||||
type: jinja
|
||||
jinja: |
|
||||
{% if rougail.proxy.proxy_mode != 'Manual proxy configuration' %}
|
||||
the proxy mode is not manual
|
||||
{% endif %}
|
||||
|
||||
.. questions:: Question
|
||||
|
||||
**question**: OK then. What happens when you select the "Manual proxy configuration"?
|
||||
|
||||
Here if the user selects the "Manual proxy configuration" proxy mode,
|
||||
the the `manual` family will be disabled. This is what the jinja code says.
|
||||
Let's explain it more precisely.
|
||||
|
||||
|
||||
.. note:: The "the proxy mode is not manual" output is be used in the log outputs
|
||||
for example while
|
||||
|
||||
Why Jinja?
|
||||
---------------
|
||||
|
||||
.. questions:: What about this `Jinja` type?
|
||||
|
||||
If the :term:`Jinja` template returns some text, then the family will be `disabled`. Otherwise it is accessible.
|
||||
Deactivating a family means that we will not be able to access it as well as the variables or families included in this family.
|
||||
|
||||
.. note:: If the Jinja template does not return any text, the variable will be **enabled**.
|
||||
Here we are using the Jinja condition statement.
|
||||
|
||||
.. glossary::
|
||||
|
||||
Jinja
|
||||
|
||||
`Jinja <https://jinja.palletsprojects.com>`_ is a template engine.
|
||||
we are using Jinja in a classical way, that is, Jinja allows us to handle different cases,
|
||||
for example with the `if` statement.
|
||||
|
||||
What can be calculated?
|
||||
---------------------------
|
||||
|
||||
We have seen that the `disabled` or `hidden` properties could be calculated.
|
||||
The default values can be calculated too.
|
||||
|
||||
.. todo:: montrer un exemple de valeur par défaut calculées (type jinja)
|
||||
|
||||
.. todo:: montrer aussi ici des exemples de calculs de valeurs variables, ce qui est un des usages principaux de jinja
|
||||
|
||||
Using jinja in a dynamic family declaration
|
||||
-----------------------------------------------
|
||||
|
||||
Let's come back to the previous section's :ref:`dynamic family example <conditional_hidden_family>`\ .
|
||||
|
||||
In a dynamic family, as seen before, you have the possibility to name your identifier. In the classic declaration,
|
||||
the identifier's variable is named "identifier" by default. Sounds logical:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
"{{ identifier }}_proxy":
|
||||
description: "{{ identifier }} Proxy"
|
||||
dynamic:
|
||||
- HTTPS
|
||||
- SOCKS
|
||||
|
||||
Here the identifer's variable takes the value of the `dynamic` family parameter.
|
||||
|
||||
.. type-along:: Using a jinja calculation with a parameter
|
||||
|
||||
We have the possibility to use a given variable variable inside a jinja calculation:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_050/firefox/20-manual.yml
|
||||
:language: yaml
|
||||
:caption: firefox/20-proxy.yml
|
||||
|
||||
..
|
||||
manual:
|
||||
|
||||
use_for_https:
|
||||
description: Also use this proxy for HTTPS
|
||||
default: true
|
||||
|
||||
"{{ identifier }}_proxy":
|
||||
description: "{{ identifier }} Proxy"
|
||||
dynamic:
|
||||
- HTTPS
|
||||
- SOCKS
|
||||
hidden:
|
||||
jinja: |
|
||||
{% if my_identifier == 'HTTPS' and manual.use_for_https %}
|
||||
HTTPS is same has HTTP
|
||||
{% endif %}
|
||||
params:
|
||||
my_identifier:
|
||||
type: identifier
|
||||
description: |
|
||||
in HTTPS case if "manual.use_for_https" is set to True
|
||||
|
||||
address:
|
||||
description: "{{ identifier }} address"
|
||||
default:
|
||||
variable: manual.http_proxy.address
|
||||
|
||||
port:
|
||||
description: "{{ identifier }} port"
|
||||
default:
|
||||
variable: manual.http_proxy.port
|
||||
|
||||
version:
|
||||
description: SOCKS host version used by proxy
|
||||
choices:
|
||||
- v4
|
||||
- v5
|
||||
default: v5
|
||||
disabled:
|
||||
type: identifier
|
||||
when: 'HTTPS'
|
||||
|
||||
This can be done by defining a `my_identifier` variable.
|
||||
|
||||
This jinja variable comes from the `params` parameter of the `hidden` property.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
params:
|
||||
my_identifier:
|
||||
type: identifier
|
||||
|
||||
Here the `hidden` property's value is defined by a jinja calculation.
|
||||
In this jinja calculation we have a `my_identifier` jinja variable.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
hidden:
|
||||
jinja: |
|
||||
{% if my_identifier == 'HTTPS' and manual.use_for_https %}
|
||||
HTTPS is same has HTTP
|
||||
{% endif %}
|
||||
params:
|
||||
my_identifier:
|
||||
type: identifier
|
||||
description: |
|
||||
in HTTPS case if "manual.use_for_https" is set to True
|
||||
|
||||
.. type-along:: The `when` and `when_not` parameter notation
|
||||
|
||||
.. todo:: ça devrait être sur une autre page. déplacer cela ailleurs
|
||||
|
||||
Handling the SOCKS version
|
||||
----------------------------
|
||||
|
||||
Now we need to handle the SOCKS version as show in the firefox configuration screenshot:
|
||||
|
||||
.. image:: images/firefox_soks_version.png
|
||||
|
||||
|
||||
We'll just add a choice variable with a default value and a disabled property:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
version:
|
||||
description: SOCKS host version used by proxy
|
||||
choices:
|
||||
- v4
|
||||
- v5
|
||||
default: v5
|
||||
disabled:
|
||||
type: identifier
|
||||
when: 'HTTPS'
|
||||
|
||||
There is still something new about this YAML, though. It is the `when` parameter
|
||||
of the `disabled` property. You have two possible notations: `when` or `when_not`.
|
||||
These two notations are just a short hand of what we could express in jinja as
|
||||
this code:
|
||||
|
||||
.. code-block:: jinja
|
||||
|
||||
{% if identifier == 'HTTPS' %}
|
||||
when the identifer equals 'HTTPS' then the SOCKS version is disabled
|
||||
{% endif %}
|
||||
|
||||
And the `when_not` parameter is just the logical opposite.
|
||||
|
||||
Here is the final version of our YAML dynamic family:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_050/firefox/20-manual.yml
|
||||
:language: yaml
|
||||
:caption: firefox/20-proxy.yml
|
||||
|
||||
|
||||
.. keypoints:: Key points
|
||||
|
||||
**keywords**
|
||||
|
||||
- calculation with a jinja type calculation
|
||||
- calculation with a `when` or `when_not` parameter
|
||||
- defining a jinja internal variable in a jinja calculation
|
||||
|
||||
**progress**
|
||||
|
||||
Here we have come to the possibility of making any kind of calculations based on the state of the :term:`configuration`.
|
||||
This is an important feature to manage the stateful aspect of a configuration.
|
||||
|
||||
222
docs/tutorial/choice.rst
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
A variable with possible values
|
||||
==================================
|
||||
|
||||
.. objectives:: Objectives
|
||||
|
||||
We will learn how to define variables with predefined available values.
|
||||
|
||||
.. prerequisites:: Prerequisites
|
||||
|
||||
- We assume that Rougail's library is :ref:`installed <installation>` on your computer.
|
||||
|
||||
- It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step
|
||||
by checking out the corresponding tag of the `rougail-tutorials` git repository.
|
||||
Each tag corresponds to a stage of progress in the tutorial.
|
||||
Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
|
||||
|
||||
If you want to follow this tutorial with the help of the corresponding :tutorial:`rougail-tutorials git repository <src/branch/1.1>`,
|
||||
this workshop page corresponds to the tag :tutorial:`v1.1_010 <src/tag/v1.1_010>` in the repository:
|
||||
|
||||
::
|
||||
|
||||
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
|
||||
git switch --detach v1.1_010
|
||||
|
||||
A variable with a list of possible values
|
||||
---------------------------------------------
|
||||
|
||||
In the firefox browser, the proxy mode can be set by this way:
|
||||
|
||||
.. image:: images/firefox_02.png
|
||||
|
||||
A list of possible values for the `proxy_mode` variable is proposed.
|
||||
With Rougail there is the possibility of defining a list of available values
|
||||
with the `choices` variable's parameter:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/firefox/00-proxy.yml
|
||||
:linenos:
|
||||
:language: yaml
|
||||
:caption: The `proxy_mode` variable with the `choice` parameter
|
||||
:name: RougailDictionaryChoiceType
|
||||
|
||||
..
|
||||
---
|
||||
proxy_mode:
|
||||
description: Configure Proxy Access to the Internet
|
||||
choices:
|
||||
- No proxy
|
||||
- Auto-detect proxy settings for this network
|
||||
- Use system proxy settings
|
||||
- Manual proxy configuration
|
||||
- Automatic proxy configuration URL
|
||||
default: No proxy
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_010/firefox/00-proxy.yml>`
|
||||
|
||||
Let's run the Rougail CLI with these available values:
|
||||
|
||||
.. code-block:: text
|
||||
:class: terminal
|
||||
|
||||
rougail -m firefox/
|
||||
|
||||
We have an output like this one:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_010/config/01/output_ro.html
|
||||
:class: output
|
||||
|
||||
..
|
||||
<pre>╭────────────────────────── Caption ──────────────────────────╮
|
||||
│ Variable <span style="color: #ffd700">Default value</span> │
|
||||
│ <span style="color: #5c5cff">Undocumented variable</span> Modified value │
|
||||
│ <span style="color: #ff0000">Undocumented but modified variable</span> (<span style="color: #00aa00">Original default value</span>) │
|
||||
│ <span style="color: #ffaf00">Unmodifiable variable</span> │
|
||||
╰─────────────────────────────────────────────────────────────╯
|
||||
Variables:
|
||||
<span style="color: #5c5cff">┗━━ </span>📓 proxy_mode: <span style="color: #ffd700">No proxy</span>
|
||||
</pre>
|
||||
|
||||
`No proxy` is an available variable's value. We say that the `proxy_mode` variable is *constrained*
|
||||
by the possibilities of the `choice` parameter.
|
||||
|
||||
.. type-along:: Let's add some user datas to this structure
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/03/config.yml
|
||||
:linenos:
|
||||
:language: yaml
|
||||
:caption: A user data specification
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_010/config/03/config.yml>`
|
||||
|
||||
If we run the Rougail CLI with this user datas:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/03/cmd_ro.txt
|
||||
:class: terminal
|
||||
|
||||
..
|
||||
rougail -m firefox/ -u yaml -yf config/03/config.yml
|
||||
|
||||
We have this output:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/03/output_ro.html
|
||||
:class: output
|
||||
|
||||
..
|
||||
<pre>╭────────────── Caption ───────────────╮
|
||||
│ Variable Modified value │
|
||||
│ (<span style="color: #00aa00">⏳ Original default value</span>) │
|
||||
╰──────────────────────────────────────╯
|
||||
Variables:
|
||||
<span style="color: #5c5cff">┗━━ </span>📓 Configure Proxy Access to the Internet: Manual proxy configuration ◀ loaded from the YAML file "config/03/config.yml" (⏳ <span style="color: #00aa00">No proxy</span>)
|
||||
</pre>
|
||||
|
||||
As we set the `proxy_mode` variable from a user data file,
|
||||
we now have specified a value which is **not** a default value, and
|
||||
the output of the Rougail CLI explicitly shows that a user data value has been entered,
|
||||
it shows which user data file this value comes from, and it also indicates
|
||||
what the default value is for information purposes.
|
||||
|
||||
.. type-along:: The constraints that come with the choices
|
||||
|
||||
The `proxy_mode` variable's possible values is *constrained*.
|
||||
|
||||
We have the list of the possible (authorized) values:
|
||||
|
||||
- `No proxy`
|
||||
- `Auto-detect proxy settings for this network`
|
||||
- `Use system proxy settings`
|
||||
- `Manual proxy configuration`
|
||||
- `Automatic proxy configuration URL`
|
||||
|
||||
.. questions:: Question
|
||||
|
||||
What happens if I set a value that is not available in the choices?
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/04/config.yml
|
||||
:linenos:
|
||||
:language: yaml
|
||||
:caption: A (false) user data specification
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_010/config/04/config.yml>`
|
||||
|
||||
If we run the Rougail CLI with this user datas:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/04/cmd_ro.txt
|
||||
:class: terminal
|
||||
|
||||
..
|
||||
rougail -m firefox/ -u yaml -yf config/03/config.yml
|
||||
|
||||
We have this output:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/04/output_ro.html
|
||||
:class: output
|
||||
|
||||
We can see here that Rougail warns us about an invalid value that is not in the available choices,
|
||||
that's why this value will not be used and it falls back to the original default value.
|
||||
|
||||
But maybe this is not the behavior you need. Maybe you need to stop everything if Rougail detects that
|
||||
something is going wrong, maybe you need some kind of a strict mode.
|
||||
|
||||
Indeed, this warning can be transformed into an error.
|
||||
|
||||
If we run the Rougail CLI with this `--cli.invalid_user_datas_error` parameter:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/04/cmd_invalid.txt
|
||||
:class: terminal
|
||||
|
||||
..
|
||||
rougail -m firefox/ -u yaml -yf config/03/config.yml --cli.invalid_user_datas_error
|
||||
|
||||
Then we have an `error` output instead of a `warning` output:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/config/04/output_invalid.html
|
||||
:class: output
|
||||
|
||||
..
|
||||
<pre><span style="font-weight: bold; color: #ff0000">🛑 ERRORS</span>
|
||||
<span style="color: #ff0000">┗━━ </span>the value "foo" is an invalid choice for "proxy_mode" (Configure Proxy Access to the Internet), only "Auto-detect proxy settings for this network", "Automatic proxy configuration URL", "Manual proxy
|
||||
<span style="color: #ff0000"> </span>configuration", "No proxy" and "Use system proxy settings" are allowed, it will be ignored when loading from the YAML file "config/04/config.yml"
|
||||
</pre>
|
||||
|
||||
|
||||
|
||||
.. keypoints:: Key points progress
|
||||
|
||||
Indeed, in the Firefox configuration, it is possible to define several configuration modes,
|
||||
from no proxy at all to different kind of automatic or manual configuration modes.
|
||||
The choices, the list of available values for a variable, can help us to handle this situation.
|
||||
|
||||
**Progress**
|
||||
|
||||
To sum up, we have arrived at this point in writing a structure file like this:
|
||||
|
||||
**Structure description file**
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/firefox/00-proxy.yml
|
||||
:linenos:
|
||||
:language: yaml
|
||||
:caption: A Rougail structure file with a variable named `proxy_mode`, with a default value.
|
||||
|
||||
..
|
||||
|
||||
.. raw:: text
|
||||
|
||||
---
|
||||
proxy_mode:
|
||||
description: Configure Proxy Access to the Internet
|
||||
choices:
|
||||
- No proxy
|
||||
- Auto-detect proxy settings for this network
|
||||
- Use system proxy settings
|
||||
- Manual proxy configuration
|
||||
- Automatic proxy configuration URL
|
||||
default: No proxy
|
||||
|
||||
307
docs/tutorial/disabled.rst
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
Disabling things
|
||||
========================
|
||||
|
||||
.. objectives:: Objectives
|
||||
|
||||
In this section we will see what a disabled variable or family is, and why it can be interesting
|
||||
to assign the `disabled` property to a variable or a family.
|
||||
|
||||
We will disable a whole family here (yes, a family can disapear in the outerspace).
|
||||
|
||||
.. prerequisites:: Prerequisites
|
||||
|
||||
- We assume that Rougail's library is :ref:`installed <installation>` on your computer.
|
||||
|
||||
- It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step
|
||||
by checking out the corresponding tag of the `rougail-tutorials` git repository.
|
||||
Each tag corresponds to a stage of progress in the tutorial.
|
||||
Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
|
||||
|
||||
If you want to follow this tutorial with the help of the corresponding :tutorial:`rougail-tutorials git repository <src/branch/1.1>`,
|
||||
this workshop page corresponds to the tags :tutorial:`v1.1_040 <src/tag/v1.1_040>` to :tutorial:`v1.1_044 <src/tag/v1.1_044>`
|
||||
in the repository.
|
||||
|
||||
::
|
||||
|
||||
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
|
||||
git switch --detach v1.1_040
|
||||
|
||||
A disabled family
|
||||
---------------------------------------------
|
||||
|
||||
First, a definition:
|
||||
|
||||
.. glossary::
|
||||
|
||||
property
|
||||
|
||||
A property is a state (`disabled`, `mandatory`, `frozen`, `hidden`...)
|
||||
of a family, a subfamily or a variable.
|
||||
These properties change the usual behavior of a variable or family.
|
||||
|
||||
Here we are going to assign the `disabled` property to the `manual` family:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_040/firefox/10-manual.yml
|
||||
:language: yaml
|
||||
:caption: The `manual` family has the `disabled` property set in this :file:`firefox/10-manual.yml` structure file
|
||||
|
||||
..
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
manual:
|
||||
description: Manual proxy configuration
|
||||
disabled: true
|
||||
|
||||
http_proxy: # HTTP Proxy
|
||||
|
||||
address:
|
||||
description: HTTP address
|
||||
type: domainname
|
||||
params:
|
||||
allow_ip: true
|
||||
|
||||
port:
|
||||
description: HTTP Port
|
||||
type: port
|
||||
default: 8080
|
||||
|
||||
.. raw:: html
|
||||
:class: terminal
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_040/config/01/cmd_ro.txt
|
||||
|
||||
..
|
||||
rougail -m firefox/ -u yaml -yf config/02/config.yml
|
||||
|
||||
The Rougail CLI outputs this:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_040/config/01/output_ro.html
|
||||
:class: output
|
||||
|
||||
We can deduce that the `manual` family is not taken into account by Rougail.
|
||||
|
||||
.. glossary::
|
||||
|
||||
disabled
|
||||
|
||||
The disabled property is a property that can be assigned to a variable or a family.
|
||||
It makes the :term:`configuration` act as if the variable or family that has this property has not even been defined.
|
||||
It simply doesn't exist. It is deactivated for the whole configuration.
|
||||
|
||||
|
||||
.. note:: Note that if a family has been disabled, all variables and sub-families that it contains are disabled.
|
||||
|
||||
If we still wanna try to assign values to variables that have been disabled:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_040/config/02/config.yml
|
||||
:language: yaml
|
||||
:caption: Here in the :file:`config/02/config.yml` user data file, we assign values to disabled variables
|
||||
|
||||
If we launch the Rougail CLI:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_040/config/02/cmd_ro.txt
|
||||
:class: terminal
|
||||
|
||||
It outputs:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_040/config/02/output_ro.html
|
||||
:class: output
|
||||
|
||||
Let's have a look at come back to our use case, we have a choice between five options
|
||||
in order to set the proxy mode:
|
||||
|
||||
.. image:: images/firefox_01.png
|
||||
|
||||
These five choices are:
|
||||
|
||||
- No proxy
|
||||
- Auto-detect proxy settings for this network
|
||||
- Use system proxy settings
|
||||
- Manual proxy configuration
|
||||
- Automatic proxy configuration URL
|
||||
|
||||
If the `Manual proxy configuration` is not selected, we don't need to set
|
||||
the `address` and `port` variables, there is no point in setting them.
|
||||
We could say that we disable them in order to expose the fact that we don't use them,
|
||||
but it's not just that: if we fill them in, there might be a problem in the overall integrity of the configuration.
|
||||
We shall fill and use in these variables in the `Manual proxy configuration` context only.
|
||||
Otherwise, **we need to disable them when they are not necessary**.
|
||||
|
||||
If we fill them in, Rougail CLI will output a warning and the :term:`operator` will wonder : "what am I doing?".
|
||||
I shall fill and use in these variables only in the `Manual proxy configuration` context.
|
||||
|
||||
.. important:: We need to **disable** variables or families that are not used
|
||||
in a given usage context.
|
||||
|
||||
.. type-along:: Disabling a group of variables is disabling a family
|
||||
|
||||
If we don't choose the manual mode, we need to **disable** these variables.
|
||||
|
||||
Note that we've placed theses variables in the `http_proxy`
|
||||
subfamily. We can then disable the whole `manual` subfamily in order to
|
||||
disable the `http_proxy` family and all the variables that are placed in it.
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_040/firefox/10-manual.yml
|
||||
:linenos:
|
||||
:language: yaml
|
||||
:caption: The `http_proxy` subfamily in the :file:`firefox/10-manual.yml` structure file
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
description: Manual proxy configuration
|
||||
disabled: true
|
||||
|
||||
http_proxy:
|
||||
description: HTTP Proxy
|
||||
|
||||
address:
|
||||
description: HTTP address
|
||||
type: domainname
|
||||
params:
|
||||
allow_ip: true
|
||||
|
||||
port:
|
||||
description: HTTP Port
|
||||
type: port
|
||||
default: 8080
|
||||
|
||||
Notice the `disabled: true` parameter set in the `manual` family.
|
||||
|
||||
|
||||
A conditional disabled family
|
||||
------------------------------
|
||||
|
||||
.. type-along:: For those who follow the tutorial with the help of the git repository
|
||||
|
||||
Now you need to checkout the `v1.1_041` version::
|
||||
|
||||
git switch --detach v1.1_041
|
||||
|
||||
What we need is a *dynamic setting* of the disable/enable property of the `manual` family.
|
||||
The idea in this section is to dynamically set the enable/disable tag according to the chosen context.
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_041/firefox/10-manual.yml
|
||||
:linenos:
|
||||
:language: yaml
|
||||
:caption: The :file:`firefox/10-manual.yml` structure file. The `manual` family dynamically enabled or disabled
|
||||
|
||||
|
||||
..
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
manual:
|
||||
description: Manual proxy configuration
|
||||
disabled:
|
||||
variable: _.proxy_mode
|
||||
when_not: Manual proxy configuration
|
||||
|
||||
http_proxy: # HTTP Proxy
|
||||
|
||||
address:
|
||||
description: HTTP address
|
||||
type: domainname
|
||||
params:
|
||||
allow_ip: true
|
||||
|
||||
port:
|
||||
description: HTTP Port
|
||||
type: port
|
||||
default: 8080
|
||||
|
||||
FIXME : trouver une autre formulation et expliquer un peu plus, notamment le "when_not"
|
||||
.. note:: The `_.` Rougail's notation means the variable in the current workspace.
|
||||
|
||||
We can see here that the `manual` family disabled or enabled property is contitionned by the `_.proxy_mode` variable's value.
|
||||
|
||||
As the default value for the `proxy_mode`'s variable is `No proxy`, the `manual` familiy is disabled.
|
||||
|
||||
Let's launch the Rougail CLI on an empty user value file:
|
||||
|
||||
.. raw:: html
|
||||
:class: terminal
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_041/config/01/cmd_ro.txt
|
||||
|
||||
..
|
||||
rougail -m firefox/ -u yaml -yf config/01/config.ym
|
||||
|
||||
We have this output:
|
||||
|
||||
.. raw:: html
|
||||
:class: output
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_041/config/01/output_ro.html
|
||||
|
||||
..
|
||||
<pre>╭──────────────────── Caption ─────────────────────╮
|
||||
│ <span style="color: #ff0000">Undocumented but modified variable</span> <span style="color: #ffd700">Default value</span> │
|
||||
╰──────────────────────────────────────────────────╯
|
||||
Variables:
|
||||
<span style="color: #5c5cff">┗━━ </span>📓 <span style="color: #ff0000">Configure Proxy Access to the Internet</span>: <span style="color: #ffd700">No proxy</span>
|
||||
</pre>
|
||||
|
||||
We can see that the `manual` family and all the variables into it are not present.
|
||||
|
||||
.. type-along:: Dynamically enabling the `manual` family
|
||||
|
||||
Now if we choose **the manual mode**, that is the `Manual proxy configuration` value for the `proxy_mode`, things become slightly different.
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_041/config/02/config.yaml
|
||||
:linenos:
|
||||
:language: yaml
|
||||
:caption: The `proxy_mode`'s manual setting in the :file:`config/03/config.yaml` user datas file
|
||||
|
||||
..
|
||||
---
|
||||
proxy_mode: Manual proxy configuration
|
||||
manual:
|
||||
http_proxy:
|
||||
address: http.proxy.net
|
||||
port: 3128
|
||||
use_for_https: false
|
||||
|
||||
If the manual mode for the proxy is not selected, then the `manual` family shall be disabled.
|
||||
On the other hand, if the manual proxy's configuration mode is selected,
|
||||
then the `manual` family is activated (enabled).
|
||||
|
||||
.. note:: Remember that this activation/deactivation of the `manual` family
|
||||
depends on the value of the `proxy_mode` variable.
|
||||
|
||||
In rougail, we can set a property's value **depending on** the value of another variable. That is, **it is conditioned by** another variable.
|
||||
|
||||
FIXME expliquer ici mieux le "when_not"
|
||||
|
||||
.. rubric:: Explanation
|
||||
|
||||
Here we have the `disabled` property like this:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
disabled:
|
||||
type: variable
|
||||
variable: proxy_mode
|
||||
when_not: 'Manual proxy configuration'
|
||||
|
||||
Here the `disabled` property **depends on** the value of another variable.
|
||||
The `variable` parameter allows you to define the name of the target variable on which the `disabled` property depends.
|
||||
|
||||
.. keypoints:: Key points progress
|
||||
|
||||
**summary**
|
||||
|
||||
We have the ability to build a contextual setting:
|
||||
|
||||
- if `proxy_mode` is not `'Manual proxy configuration'` the `manual` family is disabled
|
||||
- if `proxy_mode == 'Manual proxy configuration'` the `manual` family is enabled
|
||||
|
||||
**Keywords**
|
||||
|
||||
- We now know what a *property* is, we have seen in details the :term:`disabled` property
|
||||
- We can target a variable's value in the `disabled` property's value,
|
||||
we call it a variable based contextual disabled family
|
||||
|
||||
456
docs/tutorial/domainname.rst
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
Some suitable types
|
||||
=====================
|
||||
|
||||
.. objectives:: Objectives
|
||||
|
||||
There isn't just the basic `string` available type, here we will discover new
|
||||
variable types, for example the `boolean` type, and even types that are much more
|
||||
suited to our use case, such as `domainname` or `port`.
|
||||
|
||||
.. prerequisites:: Prerequisites
|
||||
|
||||
- We assume that Rougail's library is :ref:`installed <installation>` on your computer.
|
||||
|
||||
- It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step
|
||||
by checking out the corresponding tag of the `rougail-tutorials` git repository.
|
||||
Each tag corresponds to a stage of progress in the tutorial.
|
||||
Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
|
||||
|
||||
If you want to follow this tutorial with the help of the corresponding :tutorial:`rougail-tutorials git repository <src/branch/1.1>`,
|
||||
this workshop page corresponds to the tags :tutorial:`v1.1_030 <src/tag/v1.1_030>` to :tutorial:`v1.1_033 <src/tag/v1.1_033>`
|
||||
in the repository.
|
||||
|
||||
::
|
||||
|
||||
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
|
||||
git switch --detach v1.1_030
|
||||
|
||||
.. type-along:: Let's recap how far we've come
|
||||
|
||||
We have an `http_proxy` family with an `address` variable in it.
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_022/firefox/10-manual.yml
|
||||
:language: yaml
|
||||
:caption: An `address` variable in the `http_proxy` family
|
||||
|
||||
..
|
||||
manual: # Manual proxy configuration
|
||||
http_proxy: # HTTP Proxy
|
||||
address:
|
||||
description: HTTP address
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_022/firefox/10-manual.yml>`
|
||||
|
||||
|
||||
A variable with type `domainname`
|
||||
-----------------------------------
|
||||
|
||||
We will add a business types to our `address` variable:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/firefox/10-manual.yml
|
||||
:language: yaml
|
||||
:caption: An `address` variable in the `http_proxy` family
|
||||
|
||||
..
|
||||
manual: # Manual proxy configuration
|
||||
http_proxy: # HTTP Proxy
|
||||
address:
|
||||
description: HTTP address
|
||||
type: domainname
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_030/firefox/10-manual.yml>`
|
||||
|
||||
Notice that with this `type: domainname` we have assigned the `domainname` business type to this variable.
|
||||
Assigning a type is convenient for reading, but what else does it bring?
|
||||
|
||||
Well, with a correct user data like this one:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/01/config.yml
|
||||
:language: yaml
|
||||
:caption: A domain name user data setting
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
http_proxy:
|
||||
address: net.example
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_030/config/01/config.yml>`
|
||||
|
||||
if we launch the Rougail CLI on it:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/01/cmd_ro.txt
|
||||
:class: terminal
|
||||
|
||||
..
|
||||
rougail -m firefox/ -u yaml -yf config/01/config.yml
|
||||
|
||||
We have this output:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/01/output_ro.html
|
||||
:class: output
|
||||
..
|
||||
<pre>╭──────── Caption ────────╮
|
||||
│ Variable <span style="color: #ffd700">Default value</span> │
|
||||
│ <span style="color: #00aa00">Modified value</span> │
|
||||
╰─────────────────────────╯
|
||||
Variables:
|
||||
<span style="color: #5c5cff">┣━━ </span>📓 Configure Proxy Access to the Internet: <span style="color: #ffd700">No proxy</span>
|
||||
<span style="color: #5c5cff">┗━━ </span>📂 Manual proxy configuration
|
||||
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📂 HTTP Proxy
|
||||
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📓 HTTP address: <span style="color: #00aa00">example.net</span> ◀ loaded from the YAML file
|
||||
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span>"config/01/config.yml"
|
||||
</pre>
|
||||
|
||||
|
||||
And we don't really see any change associated with the fact that we have assigned
|
||||
a type to this variable. But if we assign this (wrong) user data:
|
||||
|
||||
.. type-along:: A domain name has no space in it
|
||||
|
||||
Let's have a look at an example of user setting that does not fit the
|
||||
`domainname` type:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/03/config.yml
|
||||
:language: yaml
|
||||
:caption: An invalid domain name for the :file:`config/03/config.yml` user data setting
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
http_proxy:
|
||||
address: bla bla
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_030/config/03/config.yml>`
|
||||
|
||||
The value is obviously not a domain name, then when we will launch the Rougail CLI:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/03/cmd_invalid.txt
|
||||
:class: terminal
|
||||
|
||||
..
|
||||
rougail -m firefox/ -u yaml -yf config/03/config.yml --cli.invalid_user_datas_error
|
||||
|
||||
we then have this output:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/03/output_invalid.html
|
||||
:class: error
|
||||
|
||||
..
|
||||
<pre><span style="font-weight: bold; color: #ff0000">🛑 ERRORS</span>
|
||||
<span style="color: #ff0000">┗━━ </span>the value "bla bla" is an invalid domain name for
|
||||
<span style="color: #ff0000"> </span>"manual.http_proxy.address" (HTTP address), must not be an IP, it will be
|
||||
<span style="color: #ff0000"> </span>ignored when loading from the YAML file "config/02/config.yml"
|
||||
</pre>
|
||||
|
||||
A variable with type's parameters
|
||||
-------------------------------------
|
||||
|
||||
What if we set an IP address instead of a domain name?
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/02/config.yml
|
||||
:language: yaml
|
||||
:caption: A domain name in the :file:`config/02/config.yml` user data setting with an IP address
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
http_proxy:
|
||||
address: 19.168.230.51
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_030/config/02/config.yml>`
|
||||
|
||||
With a value that *is not a domain name* but an IP address, then when we will launch the Rougail CLI
|
||||
we will see a little problem:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/02/cmd_invalid.txt
|
||||
:class: terminal
|
||||
|
||||
..
|
||||
rougail -m firefox/ -u yaml -yf config/02/config.yml --cli.invalid_user_datas_error
|
||||
|
||||
we then have this output:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_030/config/02/output_invalid.html
|
||||
:class: error
|
||||
|
||||
..
|
||||
<pre><span style="font-weight: bold; color: #ff0000">🛑 ERRORS</span>
|
||||
<span style="color: #ff0000">┗━━ </span>the value "192.168.0.1" is an invalid domain name for
|
||||
<span style="color: #ff0000"> </span>"manual.http_proxy.address" (HTTP address), must not be an IP, it will be
|
||||
<span style="color: #ff0000"> </span>ignored when loading from the YAML file "config/02/config.yml"
|
||||
</pre>
|
||||
|
||||
We observe that an error has been raised because an IP address is not a domain name.
|
||||
Therefore, a type validation is taking place because we declared the type `domainname`.
|
||||
|
||||
.. questions:: Question
|
||||
|
||||
OK I agree with the `domainname` necessary type validation, but what if I want to specify
|
||||
an IP address as a user value for this `address` variable?
|
||||
Because it is therefore simply impossible to do so now.
|
||||
|
||||
Is there a way for my `address` variable to accept an IP address?
|
||||
|
||||
Well, it is possible to configure the type so that it accepts IP addresses.
|
||||
We need to specify whether our variable accepts to be filled using an IP or a domain name only.
|
||||
This is where the ability to parameterize our variable comes in.
|
||||
|
||||
.. type-along:: For those who follow the tutorial with the help of the git repository
|
||||
|
||||
Now you need to checkout the `v1.1_031` version::
|
||||
|
||||
git switch --detach v1.1_031
|
||||
|
||||
Let's add a type parameter named `allow_ip`:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_031/firefox/10-manual.yml
|
||||
:language: yaml
|
||||
:caption: The `allow_ip` type parameter set in the :file:`firefox/10-manual.yml` structure file
|
||||
:linenos:
|
||||
..
|
||||
---
|
||||
manual:
|
||||
description: Manual proxy configuration
|
||||
|
||||
http_proxy:
|
||||
description: HTTP Proxy
|
||||
|
||||
address:
|
||||
description: HTTP address
|
||||
type: domainname
|
||||
params:
|
||||
allow_ip: true
|
||||
|
||||
The params allow the domain name `address` variable to be set with IPs.
|
||||
|
||||
.. glossary::
|
||||
|
||||
type parameter
|
||||
|
||||
A type parameter is a parameter of a variable that can refine its behavior.
|
||||
It is declared by adding the `params` attribute in the variable's
|
||||
definition.
|
||||
|
||||
Now we will test with an IP address as the value for our `address` variable.
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_031/config/02/config.yml
|
||||
:language: yaml
|
||||
:caption: An IP address as a value in the :file:`config/02/config.yml` user value
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
http_proxy:
|
||||
address: 192.168.0.1
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_031/config/02/config.yml>`
|
||||
|
||||
if we launch the Rougail CLI on it:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_031/config/02/cmd_ro.txt
|
||||
:class: terminal
|
||||
|
||||
..
|
||||
rougail -m firefox/ -u yaml -yf config/02/config.yml
|
||||
|
||||
We have this output:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_031/config/02/output_ro.html
|
||||
:class: output
|
||||
|
||||
We can see that the IP address value has been accepted.
|
||||
|
||||
A variable with type `port`
|
||||
------------------------------
|
||||
|
||||
.. type-along:: For those who follow the tutorial with the help of the git repository
|
||||
|
||||
Now you need to checkout the `v1.1_032` version::
|
||||
|
||||
git switch --detach v1.1_032
|
||||
|
||||
After the `address` variable let's add, according to our use case, a new variable of type `port`:
|
||||
|
||||
.. image:: images/firefox_port.png
|
||||
|
||||
Our structure file looks like this:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_032/firefox/10-manual.yml
|
||||
:language: yaml
|
||||
:caption: The `port` type variable in the :file:`firefox/10-manual.yml` structure file
|
||||
:linenos:
|
||||
..
|
||||
port:
|
||||
description: HTTP Port
|
||||
type: port
|
||||
default: 8080
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_032/firefox/10-manual.yml>`
|
||||
|
||||
Let's assign a value to this port:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_032/config/02/config.yml
|
||||
:language: yaml
|
||||
:caption: A user data :file:`config/02/config.yml` setting a value to the port variable
|
||||
|
||||
..
|
||||
proxy_mode: Manual proxy configuration
|
||||
manual:
|
||||
http_proxy:
|
||||
address: example.net
|
||||
port: 3128
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_032/config/02/config.yml>`
|
||||
|
||||
If we launch the Rougail CLI:
|
||||
|
||||
.. raw:: html
|
||||
:class: terminal
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_032/config/02/cmd_ro.txt
|
||||
|
||||
We have this output:
|
||||
|
||||
.. raw:: html
|
||||
:class: output
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_032/config/02/output_ro.html
|
||||
|
||||
..
|
||||
<pre>╭─────────────────────────── Caption ────────────────────────────╮
|
||||
│ Variable <span style="color: #00aa00">Modified value</span> │
|
||||
│ <span style="color: #ff0000">Undocumented but modified variable</span> (⏳ Original default value) │
|
||||
╰────────────────────────────────────────────────────────────────╯
|
||||
Variables:
|
||||
<span style="color: #5c5cff">┣━━ </span>📓 <span style="color: #ff0000">Configure Proxy Access to the Internet</span>: <span style="color: #00aa00">Manual proxy configuration</span> ◀
|
||||
<span style="color: #5c5cff">┃ </span>loaded from the YAML file "config/02/config.yml" (⏳ No proxy)
|
||||
<span style="color: #5c5cff">┗━━ </span>📂 Manual proxy configuration
|
||||
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📂 HTTP Proxy
|
||||
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┣━━ </span>📓 <span style="color: #ff0000">HTTP address</span>: <span style="color: #00aa00">example.net</span> ◀ loaded from the YAML file
|
||||
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┃ </span>"config/02/config.yml"
|
||||
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📓 <span style="color: #ff0000">HTTP Port</span>: <span style="color: #00aa00">3128</span> ◀ loaded from the YAML file
|
||||
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span>"config/02/config.yml" (⏳ 8080)
|
||||
</pre>
|
||||
|
||||
How can we know what validations the port type performs on the value assigned to the variable?
|
||||
|
||||
There are a number of validations that are carried out with this `port` type.
|
||||
Now let's assign a value that is outside the allowed ports:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_032/config/03/config.yml
|
||||
:language: yaml
|
||||
:caption: A user value in :file:`config/03/config.yml` that is not allowed
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_032/config/03/config.yml>`
|
||||
|
||||
Again, we launch the Rougail CLI:
|
||||
|
||||
.. raw:: html
|
||||
:class: terminal
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_032/config/03/cmd_invalid.txt
|
||||
|
||||
.. rougail -m firefox/ -u yaml -yf config/03/config.yml --cli.invalid_user_datas_error
|
||||
|
||||
And we have this output:
|
||||
|
||||
.. raw:: html
|
||||
:class: error
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/commit/v1.1_032/config/03/output_invalid.html
|
||||
|
||||
..
|
||||
<pre><span style="font-weight: bold; color: #ff0000">🛑 ERRORS</span>
|
||||
<span style="color: #ff0000">┗━━ </span>the value "100000" is an invalid port for "manual.http_proxy.port" (HTTP
|
||||
<span style="color: #ff0000"> </span>Port), must be between 1 and 65535, it will be ignored when loading from the
|
||||
<span style="color: #ff0000"> </span>YAML file "config/03/config.yml"
|
||||
</pre>
|
||||
|
||||
We observe that, as with the `domainname` type, a number of validations are performed
|
||||
to ensure that the value assigned to this variable conforms to the `port` type.
|
||||
|
||||
A variable with type `boolean`
|
||||
-----------------------------------
|
||||
|
||||
.. type-along:: For those who follow the tutorial with the help of the git repository
|
||||
|
||||
Now you need to checkout the `v1.1_033` version::
|
||||
|
||||
git switch --detach v1.1_033
|
||||
|
||||
Let's add one more variable in the `manual` family, with a much more basic type: `boolean`.
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_033/firefox/20-manual.yml
|
||||
:language: yaml
|
||||
:caption: A new structure file :file:`firefox/20-manual.yml` with one variable
|
||||
|
||||
..
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
manual:
|
||||
|
||||
use_for_https: true # Also use this proxy for HTTPS
|
||||
...
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_033/firefox/20-manual.yml>`
|
||||
|
||||
.. note::
|
||||
|
||||
- it is not necessary to declare the variable as a boolean type,
|
||||
the type is *inferred* by the presence of the `true` default value
|
||||
|
||||
- we have decided to create a new structure file :file:`firefox/20-manual.yml`.
|
||||
This is not necessary but usefull, please have a look at the :ref:`structure file organization and naming conventions <namingconvention>`
|
||||
|
||||
- here we reuse the `manual` existing family name that has already been declared in the :file:`firefox/10-manual.yml` structure file.
|
||||
The content in the :file:`firefox/20-manual.yml` will be added to the existing `manual` family.
|
||||
Note that if two different structure files are loaded by Rougail and if they declare the same family name,
|
||||
then **the declarations are concatenated in the family name**.
|
||||
|
||||
Let's switch this boolean variable to a `false` value, to do this we will add
|
||||
this :file:`config/02/config.yml` user data file in the :term:`configuration`:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_033/config/02/config.yml
|
||||
:language: yaml
|
||||
:caption: A :file:`config/02/config.yml` user data file with false as a value for `use_for_https`
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
http_proxy:
|
||||
address: example.net
|
||||
use_for_https: false
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_033/config/02/config.yml>`
|
||||
|
||||
|
||||
Let's run the Rougail CLI:
|
||||
|
||||
.. raw:: html
|
||||
:class: terminal
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_033/config/02/cmd_ro.txt
|
||||
|
||||
..
|
||||
rougail -m firefox/ -u yaml -yf config/02/config.yml
|
||||
|
||||
the result is:
|
||||
|
||||
.. raw:: html
|
||||
:class: output
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_033/config/02/output_ro.html
|
||||
|
||||
We could of course perform several tests on type validators, but here the validation is simple: only two values are allowed (`true` or `false`).
|
||||
|
||||
.. keypoints:: let's review the key points
|
||||
|
||||
- we can assign a `domainname` type to a variable
|
||||
- we can set a :term:`type parameter` to a `domainname` variable to refine their typing behavior
|
||||
- we can assign a `port` type to a variable
|
||||
- we know how to set a `boolean` type variable to `true` or `false`
|
||||
267
docs/tutorial/dynfam.rst
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
.. _dynfam:
|
||||
|
||||
A dynamic family
|
||||
================
|
||||
|
||||
.. objectives:: Objectives
|
||||
|
||||
In this section we will learn how to create a dynamically built family.
|
||||
|
||||
In a dynamically built family, instead of duplicating the definition of
|
||||
identical variables in several families, they can be generated automatically.
|
||||
|
||||
.. prerequisites:: Reminder
|
||||
|
||||
We handled the HTTPS mode in the previous section. But there's more modes to handle.
|
||||
Let's turn back to the firefox's configuration page:
|
||||
|
||||
.. image:: images/soksv5.png
|
||||
|
||||
We see that we need to handle the SOCKS configuration in addition to the HTTPS configuration.
|
||||
Moreover, we can see that these two groups of variables are similar in the structure:
|
||||
they both have a host and a port.
|
||||
|
||||
Creating a generic family
|
||||
----------------------------
|
||||
|
||||
There are two proxies that are to be configured :
|
||||
|
||||
- the HTTPS proxy
|
||||
- the SOCKS proxy
|
||||
|
||||
As they have the same structure, would it be possible to define the two of them
|
||||
in one shot?
|
||||
|
||||
.. note:: It's not the place here to describe what the HTTP and SOCKS protocols are.
|
||||
The interesting point here is that they are very similar in our firefox's
|
||||
configuration and that we can do batch processing.
|
||||
|
||||
With Rougail, it is possible to create some kind of a model of family.
|
||||
Kind of a generic family declaration.
|
||||
We call this generic family creation process a "dynamic creation" because as we will see below,
|
||||
these families exist at the very moment we define their **identifiers**.
|
||||
|
||||
First, here is what we need to make (without identifiers):
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
https_proxy:
|
||||
description: HTTPS Proxy
|
||||
...
|
||||
|
||||
address:
|
||||
description: HTTPS address
|
||||
...
|
||||
|
||||
port:
|
||||
description: HTTPS Port
|
||||
...
|
||||
|
||||
sock_proxy:
|
||||
description: SOCKS Proxy
|
||||
...
|
||||
|
||||
address:
|
||||
description: SOCKS address
|
||||
...
|
||||
|
||||
port:
|
||||
description: SOCKS Port
|
||||
...
|
||||
|
||||
Now with identifiers, we have the ability to declare our families this way:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
"{{ identifier }}_proxy":
|
||||
description: "{{ identifier }} Proxy"
|
||||
dynamic:
|
||||
- HTTPS
|
||||
- SOCKS
|
||||
|
||||
address:
|
||||
description: "{{ identifier }} address"
|
||||
|
||||
port:
|
||||
description: "{{ identifier }} port"
|
||||
|
||||
What is exactly an identifier?
|
||||
-------------------------------
|
||||
|
||||
If you know a YAML declaration tool named Ansible,
|
||||
the variable used to iterate over multiple values in a task is called an **`item`**.
|
||||
|
||||
It is used in the context of a loop. For example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- name: Loop example with 'item'
|
||||
ansible.builtin.debug:
|
||||
msg: "The current value is {{ item }}"
|
||||
loop:
|
||||
- value1
|
||||
- value2
|
||||
- value3
|
||||
|
||||
This code will output:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
The current value is value1
|
||||
The current value is value2
|
||||
The current value is value3
|
||||
|
||||
In the Rougail context, we name this item an identifier because it is an item
|
||||
that allow us to define dynamically family names.
|
||||
|
||||
.. glossary::
|
||||
|
||||
identifier
|
||||
|
||||
In the :ref:`dynamic family creation field <dynfam>` we call an identifier
|
||||
an item that defines a family name. An item is a variable on which an iteration
|
||||
on keywords will be carried out.
|
||||
|
||||
An :term:`identifier` is a local variable, used only for creating multiple
|
||||
iterations, used for creating multiple families in only one declaration.
|
||||
|
||||
It allows us to declare very similar families in a more generic way.
|
||||
|
||||
Here is the syntax we are using that allows the declaration of multiple families at one time:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
"{{ identifier }}_proxy":
|
||||
description: "{{ identifier }} Proxy"
|
||||
dynamic:
|
||||
- HTTPS
|
||||
- SOCKS
|
||||
|
||||
This identifier is a parameter that enables us to create two families named `https_proxy` and `socks_proxy`:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
https_proxy:
|
||||
description: "HTTPS Proxy"
|
||||
|
||||
socks_proxy:
|
||||
description: "SOCKS Proxy"
|
||||
|
||||
.. attention:: Be careful when choosing your identifiers items: pay attention that the family
|
||||
that will be dynamically created has not been declared before in some other
|
||||
YAML structure file.
|
||||
|
||||
If you define a dynamic family with the `https` item that will
|
||||
build a `https_proxy` family and if this familiy already exists,
|
||||
then rougail will raise a family/variable override warning.
|
||||
|
||||
When choosing a name,
|
||||
|
||||
- rougail will put it in lowercase
|
||||
- only ASCII and `_` characters are allowed.
|
||||
|
||||
When we launch the rougail command line, we can have a look at the concrete families and variables that have appear:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
rougail -m structfile/proxy.yml -u yaml --yaml.filename userdata/proxy.yml
|
||||
╭─────────────────── Caption ────────────────────╮
|
||||
│ Variable Default value │
|
||||
│ Unmodifiable variable Modified value │
|
||||
│ (Original default value) │
|
||||
╰────────────────────────────────────────────────╯
|
||||
Variables:
|
||||
┗━━ 📂 Manual proxy configuration
|
||||
┣━━ 📂 HTTP Proxy
|
||||
┃ ┣━━ 📓 HTTP address: ... (loaded from the YAML file "userdata/proxy.yml")
|
||||
┃ ┗━━ 📓 HTTP Port: ... (8080 - loaded from the YAML file "userdata/proxy.yml")
|
||||
┣━━ 📓 Also use this proxy for HTTPS: true
|
||||
┣━━ 📂 HTTPS Proxy
|
||||
┃ ┣━━ 📓 HTTPS address: ...
|
||||
┃ ┗━━ 📓 HTTPS port: ...
|
||||
┗━━ 📂 SOCKS Proxy
|
||||
┣━━ 📓 SOCKS address: ...
|
||||
┗━━ 📓 SOCKS port: ...
|
||||
|
||||
We can see that the dynamic family has created:
|
||||
|
||||
- an `HTTPS Proxy` family
|
||||
- a `SOCKS Proxy` family
|
||||
|
||||
as we wanted, containing an address and a port.
|
||||
|
||||
|
||||
.. todo:: déplacer ce hidden dans une autre page
|
||||
|
||||
.. _conditional_hidden_family:
|
||||
|
||||
A conditional hidden familiy
|
||||
--------------------------------
|
||||
|
||||
Here is the final YAML version of the HTTPS and SOCKS proxy families:
|
||||
|
||||
|
||||
We have added:
|
||||
|
||||
- a conditional hidden family property
|
||||
- a default value
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_037/firefox/20-manual.yml
|
||||
:language: yaml
|
||||
:caption: firefox/20-proxy.yml
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
|
||||
use_for_https:
|
||||
description: Also use this proxy for HTTPS
|
||||
default: true
|
||||
|
||||
"{{ identifier }}_proxy":
|
||||
description: "{{ identifier }} Proxy"
|
||||
dynamic:
|
||||
- HTTPS
|
||||
- SOCKS
|
||||
hidden:
|
||||
variable: manual.use_for_https
|
||||
|
||||
address:
|
||||
description: "{{ identifier }} address"
|
||||
default:
|
||||
variable: manual.http_proxy.address
|
||||
|
||||
port:
|
||||
description: "{{ identifier }} port"
|
||||
default:
|
||||
variable: manual.http_proxy.port
|
||||
|
||||
|
||||
The conditional property is this one:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
hidden:
|
||||
variable: manual.use_for_https
|
||||
|
||||
it uses `use_for_https` variable:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
|
||||
use_for_https:
|
||||
description: Also use this proxy for HTTPS
|
||||
default: true
|
||||
|
||||
.. keypoints:: Key points
|
||||
|
||||
- We now know what a dynamic family is, with its identifier.
|
||||
- we now how to create default values for a variable that is calculated
|
||||
because it retrieves the value of another variable.
|
||||
- we know how to hide conditionaly a family with the same mechanism,
|
||||
that is a calculated value. It is calculated because there is a dependancy
|
||||
over another variable.
|
||||
|
||||
We will see other types of calculation in the next section.
|
||||
|
||||
|
||||
268
docs/tutorial/family.rst
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
Group variables inside families
|
||||
=================================
|
||||
|
||||
.. objectives:: Objectives
|
||||
|
||||
We will learn how to:
|
||||
|
||||
- create a :term:`family`
|
||||
- gather :term:`variable`\ s into a :term:`family`
|
||||
- make a variable within a variable, which turns this variable container into being a family
|
||||
|
||||
.. prerequisites:: Prerequisites
|
||||
|
||||
- We assume that Rougail's library is :ref:`installed <installation>` on your computer.
|
||||
|
||||
- It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step
|
||||
by checking out the corresponding tag of the `rougail-tutorials` git repository.
|
||||
Each tag corresponds to a stage of progress in the tutorial.
|
||||
Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
|
||||
|
||||
If you want to follow this tutorial with the help of the corresponding :tutorial:`rougail-tutorials git repository <src/branch/1.1>`,
|
||||
this workshop page corresponds to the tags :tutorial:`v1.1_020 <src/tag/v1.1_020>` to :tutorial:`v1.1_022 <src/tag/v1.1_022>`
|
||||
in the repository.
|
||||
|
||||
::
|
||||
|
||||
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
|
||||
git switch --detach v1.1_020
|
||||
|
||||
.. type-along:: Let's recap how far we've come
|
||||
|
||||
We have this choice variable in its structure definition file:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_010/firefox/00-proxy.yml
|
||||
:linenos:
|
||||
:language: yaml
|
||||
:caption: The `proxy_mode` choice variable in the :file:`firefox/00-proxy.yml` structure file
|
||||
|
||||
..
|
||||
---
|
||||
proxy_mode:
|
||||
description: Configure Proxy Access to the Internet
|
||||
choices:
|
||||
- No proxy
|
||||
- Auto-detect proxy settings for this network
|
||||
- Use system proxy settings
|
||||
- Manual proxy configuration
|
||||
- Automatic proxy configuration URL
|
||||
default: No proxy
|
||||
|
||||
.. We're gonna put it in a :term:`family`.
|
||||
|
||||
In short, let's describe our `proxy_mode` variable like this:
|
||||
|
||||
.. confval:: proxy_mode
|
||||
:type: `choice`
|
||||
:default: No proxy
|
||||
|
||||
Proxy mode's settings
|
||||
|
||||
Now we will define new variables, and other structure definitions.
|
||||
For the sake of clarity, we will put the structure definitions in separate files.
|
||||
Please have a look at the :ref:`file naming and organizing convention <namingconvention>`.
|
||||
|
||||
Here we made a :file:`firefox/00-proxy.yml` structure file and we're gonna make
|
||||
a new structure file named :file:`firefox/10-manual.yml`::
|
||||
|
||||
.
|
||||
└── firefox
|
||||
├── 00-proxy.yml
|
||||
└── 10-manual.yml
|
||||
|
||||
Creating a new family
|
||||
-----------------------
|
||||
|
||||
Let's create a family named `manual` which obviously corresponds to the proxy's manual configuration choice.
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_020/firefox/10-manual.yml
|
||||
:language: yaml
|
||||
:caption: A family structure file description named `manual` in a :file:`firefox/10-manual.yml` file
|
||||
:name: RougailManualFamily
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
description: Manual proxy configuration
|
||||
type: family
|
||||
|
||||
We can see that we have defined a :term:`family` here, and this family is *empty*
|
||||
which means that this family is a container variable that contains no variable yet.
|
||||
|
||||
.. warning::
|
||||
|
||||
If a family is empty, we need to specify the :term:`family` type here because if we don't,
|
||||
the Rougail's type engine will infer it by default as a :term:`variable`.
|
||||
We have to force the family type inference.
|
||||
|
||||
It's because we don't have set any :term:`variable` inside yet. When we will have a variable inside of this family,
|
||||
we will make a YAML block (to create a block in YAML, you just need to indent the lines) and the Rougail's type inference engine will implicitely infer the variable's container as a family type.
|
||||
|
||||
Or a sub family
|
||||
----------------
|
||||
|
||||
.. type-along:: For those who follow the tutorial with the help of the git repository
|
||||
|
||||
Now you need to checkout the `v1.1_021` version::
|
||||
|
||||
git switch --detach v1.1_021
|
||||
|
||||
.. glossary::
|
||||
|
||||
sub family
|
||||
|
||||
A sub family is a family inside a family.
|
||||
|
||||
Creating a family hierarchy of families (family inside a family) is very easy:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_021/firefox/10-manual.yml
|
||||
:language: yaml
|
||||
:caption: A rougail structure description file with a hierarchy.
|
||||
:name: RougailFirstFamilyHierarchy
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
description: Manual proxy configuration
|
||||
type: family
|
||||
|
||||
http_proxy:
|
||||
description: HTTP Proxy
|
||||
type: family
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_021/firefox/00-proxy.yml>`
|
||||
|
||||
Here in our use case we used the :term:`short-hand declaration mode <short-hand notation>`
|
||||
to declare our `manual` family:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
manual: # Manual proxy configuration
|
||||
|
||||
And the `http_proxy` family lives inside of this `manual` family.
|
||||
We therefore created a hierarchy of families.
|
||||
|
||||
Putting a variable inside of a family or a sub family
|
||||
-----------------------------------------------------------
|
||||
|
||||
.. type-along:: For those who follow the tutorial with the help of the git repository
|
||||
|
||||
Now you need to checkout the `v1.1_022` version::
|
||||
|
||||
git switch --detach v1.1_022
|
||||
|
||||
We are going to put a variable inside of a family or a sub family
|
||||
|
||||
Let's create a variable in the `http_proxy` family.
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_022/firefox/10-manual.yml
|
||||
:language: yaml
|
||||
:caption: An `address` variable in the `http_proxy` family
|
||||
:name: RougailVariableInSubFamily
|
||||
|
||||
..
|
||||
---
|
||||
manual:
|
||||
description: Manual proxy configuration
|
||||
type: family
|
||||
|
||||
http_proxy:
|
||||
description: HTTP Proxy
|
||||
type: family
|
||||
|
||||
address:
|
||||
description: HTTP address
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_022/firefox/10-manual.yml>`
|
||||
|
||||
Now that the :confval:`address` variable is declared, the :term:`operator` can set :term:`a value <value>` to it.
|
||||
|
||||
In short, let's describe our `address` variable like this:
|
||||
|
||||
.. confval:: address
|
||||
:default: None
|
||||
|
||||
This is the HTTP address of the proxy
|
||||
|
||||
We have reached the definition of the address in the `http_proxy` family; there will be other variables to define in this family.
|
||||
|
||||
.. image:: images/firefox_manual_family.png
|
||||
|
||||
.. type-along:: Assigning a user value
|
||||
|
||||
Now we need to set a value for the :confval:`address` variable,
|
||||
otherwise we will get an error if we try to access this variable:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_022/config/01/output_ro.html
|
||||
:class: error-box
|
||||
|
||||
..
|
||||
<pre>🛑 ERRORS
|
||||
<span style="color: #ff0000">┣━━ </span>The following variables are mandatory but have no value:
|
||||
<span style="color: #ff0000">┗━━ </span> - manual.http_proxy.address (HTTP address)
|
||||
</pre>
|
||||
|
||||
.. type-along:: Let's set user values in a user data file
|
||||
|
||||
Here is a user data file sample:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_022/config/02/config.yml
|
||||
:language: yaml
|
||||
:caption: A user file named :file:`config/03/config.yml` with a value set for the `address` variable
|
||||
:name: RougailAddresseVariableUserValue
|
||||
|
||||
..
|
||||
---
|
||||
proxy_mode: Manual proxy configuration
|
||||
manual:
|
||||
http_proxy:
|
||||
address: example.net
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_022/config/02/config.yml>`
|
||||
|
||||
Let's validate the consitency of the :term:`configuration`:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_022/config/02/cmd_ro.txt
|
||||
:class: terminal
|
||||
|
||||
..
|
||||
rougail -m firefox/ -u yaml -yf config/02/config.yml
|
||||
|
||||
Everything is OK:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_022/config/02/output_ro.html
|
||||
:class: output
|
||||
|
||||
..
|
||||
<pre>╭──────── Caption ────────╮
|
||||
│ Variable <span style="color: #ffd700">Default value</span> │
|
||||
│ Modified value │
|
||||
╰─────────────────────────╯
|
||||
Variables:
|
||||
<span style="color: #5c5cff">┣━━ </span>📓 Configure Proxy Access to the Internet: <span style="color: #ffd700">No proxy</span>
|
||||
<span style="color: #5c5cff">┗━━ </span>📂 Manual proxy configuration
|
||||
<span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📂 HTTP Proxy
|
||||
<span style="color: #5c5cff"> </span><span style="color: #5c5cff"> </span><span style="color: #5c5cff">┗━━ </span>📓 HTTP address: example.net ◀ loaded from the YAML file "config/02/config.yml"
|
||||
</pre>
|
||||
|
||||
Let's recap about the user data. We can see in this Rougail CLI output that:
|
||||
|
||||
- the `proxy_mode` value is set by default by the :term:`integrator`
|
||||
- the `address` value is has been set by an :term:`operator`
|
||||
|
||||
.. keypoints:: Let's review the key points
|
||||
|
||||
**Keywords**
|
||||
|
||||
- we know how to define :term:`variable`\ s inside of a family
|
||||
- we now know what a :term:`mandatory` variable is and why it is necessary to assign values to the variables
|
||||
- we kwow how to set a variable's :term:`user value <user data>`
|
||||
- we have the big picture : the :term:`configuration`, which is (the structure files + the user data files)
|
||||
|
||||
**Progress**
|
||||
|
||||
- we have a :term:`family` named `manual` and a sub family named `http_proxy`
|
||||
- And we have now two variables: :confval:`proxy_mode` and :confval:`address`.
|
||||
BIN
docs/tutorial/images/QuestionaryChoice.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/tutorial/images/UserDataOutput.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
BIN
docs/tutorial/images/firefox_01.png
Normal file
|
After Width: | Height: | Size: 119 KiB |
BIN
docs/tutorial/images/firefox_02.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
docs/tutorial/images/firefox_manual_family.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/tutorial/images/firefox_manual_https.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
docs/tutorial/images/firefox_port.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
docs/tutorial/images/firefox_soks_version.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 154 KiB |
BIN
docs/tutorial/images/integrator.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/tutorial/images/operator.png
Normal file
|
After Width: | Height: | Size: 7 KiB |
BIN
docs/tutorial/images/soksv5.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
58
docs/tutorial/index.rst
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
.. _tutorial:
|
||||
|
||||
Tutorial with a real world sample
|
||||
=====================================
|
||||
|
||||
Here is a fairly complete tutorial, it is a use case that comes from the real world.
|
||||
At the end of the tutorial you will have a good understanding of Rougail.
|
||||
|
||||
.. objectives:: Objectives
|
||||
|
||||
Configuring (the setting of) your favorite web browser.
|
||||
|
||||
This tutorial will show you an example of Rougail use based on the
|
||||
*how to set a proxy* in the `Mozilla Firefox <https://www.mozilla.org/en-US/firefox/new/>`_ browser
|
||||
use case.
|
||||
|
||||
More precisely, this tutorial aims at reproducing :term:`variable`\ s behind this Mozilla Firefox settings page:
|
||||
|
||||
.. image:: images/firefox.png
|
||||
|
||||
We'll call the variables **configuration options** since that's what the variables represent in this use case.
|
||||
|
||||
.. attention:: We are not coding a Firefox plugin here.
|
||||
We are just going to handle some of the Firefox configuration settings
|
||||
with Rougail. We are just validating them.
|
||||
|
||||
The configuration option values entered by the user have to be:
|
||||
|
||||
- validated
|
||||
- consitent
|
||||
- conform
|
||||
|
||||
Let's dive into this **configuration options validation** use case.
|
||||
|
||||
.. prerequisites:: Important advice
|
||||
|
||||
It is advisable to follow this tutorial with the help of the corresponding :tutorial:`Rougail git repository tutorial <src/branch/1.1>`.
|
||||
You can instead copy/paste or download the different file contents that are explained in this tutorial step and save the files to your computer.
|
||||
However, if you use the git Rougail tutorial repository, you will have all the necessary files distributed in the correct tree structure,
|
||||
which is in our opinion much more practical.
|
||||
|
||||
::
|
||||
|
||||
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials/src/branch/1.1
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:caption: The Firefox tutorial
|
||||
|
||||
preliminary
|
||||
choice
|
||||
family
|
||||
domainname
|
||||
disabled
|
||||
boolean
|
||||
dynfam
|
||||
calculation
|
||||
|
||||
419
docs/tutorial/preliminary.rst
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
Getting started
|
||||
====================
|
||||
|
||||
Presentation of the firefox configuration options
|
||||
--------------------------------------------------
|
||||
|
||||
At first glance we can see that we have a selection of five configuration options that we need to fill in, they are highlighted here in this screenshot:
|
||||
|
||||
.. image:: images/firefox_01.png
|
||||
|
||||
We'll learn in this tutorial how to set the values of the configuration options in a clean way with the Rougail library.
|
||||
|
||||
.. objectives:: Objectives of this section
|
||||
|
||||
We will learn how to:
|
||||
|
||||
- create a :term:`structure description file <structure file>`
|
||||
- add a :term:`structure file <structure file>` format version in the structure file
|
||||
- add a :term:`variable` in the structure file and set its default :term:`value`
|
||||
|
||||
.. prerequisites:: Prerequisites
|
||||
|
||||
- We assume that Rougail's library is :ref:`installed <installation>` on your computer.
|
||||
|
||||
- It is possible to retrieve the current state of the various Rougail files manipulated in this tutorial step
|
||||
by checking out the corresponding tag of the `rougail-tutorials` git repository.
|
||||
Each tag corresponds to a stage of progress in the tutorial.
|
||||
Of course, you can also decide to copy/paste or download the tutorial files contents while following the tutorial steps.
|
||||
|
||||
If you want to follow this tutorial with the help of the corresponding :tutorial:`rougail-tutorials git repository <src/branch/1.1>`,
|
||||
this workshop page corresponds to the tags :tutorial:`v1.1_000 <src/tag/v1.1_000>` to :tutorial:`v1.1_003 <src/tag/v1.1_003>`
|
||||
in the repository:
|
||||
|
||||
::
|
||||
|
||||
git clone https://forge.cloud.silique.fr/stove/rougail-tutorials.git
|
||||
git switch --detach v1.1_000
|
||||
|
||||
Creating a structure file
|
||||
--------------------------
|
||||
|
||||
.. demo:: The folder structure
|
||||
|
||||
Here is the tree structure we want to have::
|
||||
|
||||
rougail-tutorials
|
||||
└── firefox
|
||||
└── 00-proxy.yml
|
||||
|
||||
- Let's make a :file:`rougail-tutorials` directory, with a :file:`firefox` subfolder.
|
||||
- First, we will create a :term:`structure file <structure file>`, so let's create a :file:`00-proxy.yml` file
|
||||
located in the :file:`firefox` subfolder.
|
||||
|
||||
This is an empty Rougail :term:`structure description file: <structure file>`
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_000/firefox/00-proxy.yml
|
||||
:language: yaml
|
||||
:caption: An empty Rougail structure file with only the YAML header and the version number
|
||||
:name: RougailStructVersion
|
||||
|
||||
..
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_000/firefox/00-proxy.yml>`
|
||||
|
||||
This `version` specification is just the Rougail YAML's format version specification.
|
||||
YAML files are loaded using a version 1.2 file parser. It is recommended (but not mandatory) to specify this in the file header, especially for linters.
|
||||
By now, we have an empty structure file with the format specification in it.
|
||||
|
||||
Let's add our first variable
|
||||
------------------------------
|
||||
|
||||
.. type-along:: For those who follow the tutorial with the help of the git repository
|
||||
|
||||
Now you need to checkout the `v1.1_001` version::
|
||||
|
||||
git switch --detach v1.1_001
|
||||
|
||||
|
||||
- A variable is defined at a minimum by its name.
|
||||
- A :term:`variable` lives in the :term:`structure description file <structure file>`.
|
||||
|
||||
Here we define a variable named `proxy_mode`:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_001/firefox/00-proxy.yml
|
||||
:language: yaml
|
||||
:caption: A Rougail structure file with only one variable in it
|
||||
:name: RougailDictionaryFirstVariableName
|
||||
|
||||
..
|
||||
---
|
||||
proxy_mode:
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_001/firefox/00-proxy.yml>`
|
||||
|
||||
Let's run the Rougail CLI utility command in a terminal:
|
||||
|
||||
.. code-block:: text
|
||||
:class: terminal
|
||||
|
||||
rougail -m firefox/
|
||||
|
||||
Well, we notice that we have an error:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_001/config/01/output_ro.html
|
||||
:class: error-box
|
||||
|
||||
..
|
||||
🛑 ERRORS
|
||||
┣━━ The following variables are mandatory but have no value:
|
||||
┗━━ - proxy_mode
|
||||
|
||||
It's because this first defined variable is :term:`mandatory` and needs to have a value set **but** there's no value yet.
|
||||
|
||||
We can therefore deduce the fact that:
|
||||
|
||||
.. admonition:: Fact
|
||||
|
||||
Once defined, an option configuration :term:`value` is :term:`mandatory` by default.
|
||||
That is to say, it is absolutely necessary to assign a value to this variable.
|
||||
|
||||
Rougail expects the `proxy_mode` configuration option's value to be set.
|
||||
|
||||
.. glossary::
|
||||
|
||||
mandatory
|
||||
|
||||
A variable is mandatory when a value is required, that is, `None` **is not** a possible value.
|
||||
It **must** have a defined value.
|
||||
|
||||
.. seealso:: To go further, have a look at the :tiramisu:`mandatory option <glossary.html#term-mandatory-option>`
|
||||
according to the :xref:`Tiramisu <tiramisu>` underlyning consistency system.
|
||||
You will learn that it is actually possible to disable the mandatory property behavior,
|
||||
but you need to declare it explicitely.
|
||||
|
||||
Describe the variable
|
||||
-------------------------
|
||||
|
||||
Let's add a variable's description, which is not mandatory but which is usually a good practice.
|
||||
|
||||
.. type-along:: For those who follow the tutorial with the help of the git repository
|
||||
|
||||
Now you need to checkout the `v1.1_002` version::
|
||||
|
||||
git switch --detach v1.1_002
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_002/firefox/00-proxy.yml
|
||||
:language: yaml
|
||||
:caption: A Rougail structure file with a variable and a description
|
||||
:name: RougailStructFirstVariableDescription
|
||||
|
||||
..
|
||||
---
|
||||
proxy_mode: # Configure Proxy Access to the Internet
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_002/firefox/00-proxy.yml>`
|
||||
|
||||
You have two way to define a variable's description:
|
||||
|
||||
- the verbose way:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
proxy_mode:
|
||||
description: Configure Proxy Access to the Internet
|
||||
|
||||
|
||||
- or a short-hand way, setting the description using the "`#`" YAML comment notation:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
proxy_mode: # Configure Proxy Access to the Internet
|
||||
|
||||
If we launch the Rougail CLI command:
|
||||
|
||||
.. raw:: html
|
||||
:class: terminal
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_002/config/01/cmd_rw.txt
|
||||
|
||||
we have this output:
|
||||
|
||||
.. raw:: html
|
||||
:class: output
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_002/config/01/output_rw.html
|
||||
|
||||
..
|
||||
<pre>╭──────────────────── Caption ─────────────────────╮
|
||||
│ <span style="color: #ff0000">Undocumented but modified variable</span> <span style="color: #ffd700">Default value</span> │
|
||||
╰──────────────────────────────────────────────────╯
|
||||
Variables:
|
||||
<span style="color: #5c5cff">┗━━ </span>📓 <span style="color: #ff0000">Configure Proxy Access to the Internet</span>: <span style="color: #ffd700">null</span>
|
||||
</pre>
|
||||
|
||||
We can see here that the variable's description string "Configure Proxy Access to the Internet" is used
|
||||
to refer to the `proxy_mode` variable.
|
||||
|
||||
.. note:: The description is used in UI tools and outputs instead of the variable name.
|
||||
The goal here is to provide an explanation of the variable for the user,
|
||||
not to show the technical name of the variable as defined by the :term:`integrator`.
|
||||
|
||||
Set a default value
|
||||
---------------------
|
||||
|
||||
.. type-along:: For those who follow the tutorial with the help of the git repository
|
||||
|
||||
Now you need to checkout the `v1.1_003` version::
|
||||
|
||||
git switch --detach v1.1_003
|
||||
|
||||
We will learn different ways to set a value, the first way is setting a *default* value.
|
||||
|
||||
.. glossary::
|
||||
|
||||
default value
|
||||
|
||||
A default value is a variable value that is predefined, that's why this value is placed
|
||||
right in the structure file.
|
||||
|
||||
Let's add a default value to this `proxy_mode` variable.
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_003/firefox/00-proxy.yml
|
||||
:language: yaml
|
||||
:caption: A Rougail structure file with a default value for the variable
|
||||
:name: RougailDictionaryVariableDefault
|
||||
|
||||
..
|
||||
---
|
||||
proxy_mode: No proxy # Configure Proxy Access to the Internet
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_003/firefox/00-proxy.yml>`
|
||||
|
||||
The `proxy_mode` variable requires a value, that's why we have set a `No proxy` default value.
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_003/config/01/output_ro.html
|
||||
:class: output
|
||||
|
||||
..
|
||||
<pre>╭─────── Caption ────────╮
|
||||
│ Variable <span style="color: #ffd700">Default value</span> │
|
||||
╰────────────────────────╯
|
||||
Variables:
|
||||
<span style="color: #5c5cff">┗━━ </span>📓 Configure Proxy Access to the Internet: <span style="color: #ffd700">No proxy</span>
|
||||
</pre>
|
||||
|
||||
As we have set the `proxy_mode`'s value as `No proxy` by default,
|
||||
The chosen value is indicated in the Rougail's CLI output as the default choice.
|
||||
|
||||
- here is the short-hand default setting and description:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
proxy_mode: No proxy # Configure Proxy Access to the Internet
|
||||
|
||||
- and there is the verbose way of setting a default value:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
proxy_mode:
|
||||
description: Configure Proxy Access to the Internet
|
||||
default: No proxy
|
||||
|
||||
There are some other :term:`short-hand ways <short-hand notation>` with Rougail that you may encounter
|
||||
as you read the Rougail's documentation and tutorial.
|
||||
|
||||
.. admonition:: How to set a value -- the assignment
|
||||
|
||||
A default value has been set, great. This raises a question about what a normal value is.
|
||||
|
||||
Now then how can I assign a normal value to a variable?
|
||||
|
||||
.. type-along:: The different Rougail roles and setting a variable's value
|
||||
|
||||
So far we have only talked about the actor that writes the :term:`structure files <structure file>`\ .
|
||||
The one who writes the structure file plays the *role* of the *integrator*.
|
||||
|
||||
.. glossary::
|
||||
|
||||
integrator
|
||||
|
||||
An integrator in the Rougail field is the person who writes the :term:`structure files <structure file>`\ .
|
||||
He has the responsibility of the integration process, that is,
|
||||
he defines the variables and the relationship between them, the variables that are allowed
|
||||
(or not) to be set, and so on. His responsabilites are the **structuration** and the **consistency**
|
||||
of the organisation of the variables between them.
|
||||
|
||||
Now we will talk about the one that defines the values. His role is called the operator role.
|
||||
|
||||
.. glossary::
|
||||
|
||||
operator
|
||||
|
||||
An operator in the Rougail field is the person who assigns :term:`value`\ s to the pre-defined variables,
|
||||
his responsabilities are to set variable values correctly.
|
||||
|
||||
The user :term:`value`\ s, that is the values that have been set by the operator, are of course type validated.
|
||||
The type validation is driven by the definitions in the :term:`structure file <structure file>`.
|
||||
|
||||
It is the operator's responsibility to set the user data variables values.
|
||||
The operator does not handle the structure files,
|
||||
he is responsible of other files called the :term:`user data files <user data file>`.
|
||||
|
||||
.. glossary::
|
||||
|
||||
user data
|
||||
|
||||
User datas, as opposed to structured datas, are datas that only concern the assignment of values
|
||||
and not the consistency of the variables between them.
|
||||
|
||||
The variable's values are also called **user values**.
|
||||
|
||||
The consistency field is outside of the user data scope.
|
||||
The consistency is handled in the :term:`structured datas <structured data>`\ 's scope.
|
||||
|
||||
.. important:: For now, we don't know how to disable the default `mandatory` settings,
|
||||
so if neither a default value nor a user value are set for a given variable, Rougail will raise an error.
|
||||
|
||||
.. exercise:: Folder structure update
|
||||
|
||||
Now we add a user data file named :file:`config/config.yml` in our project::
|
||||
|
||||
rougail-tutorials
|
||||
├── firefox
|
||||
│ ├── 00-proxy.yml
|
||||
└── config
|
||||
└── config.yml
|
||||
|
||||
.. type-along:: How to set a user data value
|
||||
|
||||
If the integrator has not set any default value in his structure file,
|
||||
it's up to the operator to do the job in the `config.yml` file:
|
||||
|
||||
.. extinclude:: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_003/config/02/config.yml
|
||||
:language: yaml
|
||||
:caption: A Rougail user data file :file:`config/config.yml`, with a value set.
|
||||
:name: RougailConfigDefaultValue
|
||||
|
||||
..
|
||||
---
|
||||
proxy_mode: No proxy
|
||||
|
||||
:tutorial:`Download this file from the rougail-tutorials git repository <src/tag/v1.1_003/config/02/config.yml>`
|
||||
|
||||
The operator needs to add the `-u yaml -yf config/config.yml` options to the Rougail CLI:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_003/config/02/cmd_ro.txt
|
||||
:class: terminal
|
||||
|
||||
..
|
||||
rougail -m firefox/ -u yaml -yf config/02/config.yml
|
||||
|
||||
which gives us this output:
|
||||
|
||||
.. raw:: html
|
||||
:url: https://forge.cloud.silique.fr/stove/rougail-tutorials/raw/tag/v1.1_003/config/02/output_ro.html
|
||||
:class: output
|
||||
|
||||
..
|
||||
<pre>╭──────── Caption ────────╮
|
||||
│ Variable Modified value │
|
||||
╰─────────────────────────╯
|
||||
Variables:
|
||||
<span style="color: #5c5cff">┗━━ </span>📓 proxy_mode: No proxy ◀ loaded from the YAML file "config/02/config.yml"
|
||||
</pre>
|
||||
|
||||
Now the `proxy_mode`'s new `No proxy` value is the same as the default value but we see in the Rougail CLI output that the value
|
||||
comes from the :file:`config/02/config.yml` user data file. From now on this `proxy_mode` variable's value
|
||||
is a user data value and not a default value (even if it's actually the same value).
|
||||
|
||||
.. type-along:: Structure values and user data values
|
||||
|
||||
We can see with the Rougail CLI utility where the values come from.
|
||||
It can come from an integrator's setting or from an operator's setting.
|
||||
|
||||
.. admonition:: Reminder
|
||||
|
||||
- the integrator works on structure files, he can define default value for variables
|
||||
- the operator works on user data files, he only can set user data values for variables
|
||||
|
||||
Most of the time, the integrator and the operator are one and the same person,
|
||||
here we are talking about roles and not necessarily about people.
|
||||
|
||||
.. type-along:: User data files are where the user values live
|
||||
|
||||
We need to set the values in separate files, called `user data files`.
|
||||
|
||||
.. glossary::
|
||||
|
||||
user data file
|
||||
|
||||
A user data file is a file where only :term:`user data` are set.
|
||||
|
||||
A user file is a file where there are only user data in it, users can set values, called user values --
|
||||
that is variable's values that have been set by an :term:`operator`\ .
|
||||
|
||||
see also :term:`user data`
|
||||
|
||||
.. glossary::
|
||||
|
||||
configuration
|
||||
|
||||
We call configuration the whole system structure and user values,
|
||||
and when we speak of consistency, it is in relation to this whole set.
|
||||
|
||||
.. keypoints:: Key points progress
|
||||
|
||||
**Keywords**
|
||||
|
||||
- :term:`structure file <structure file>`: structure description file
|
||||
- :term:`variable`: an option's name which has a value
|
||||
- a variable's description
|
||||
- a variable's mandatory value
|
||||
- a variable's default value
|
||||
- a variable's user value
|
||||
- the :term:`integrator` and :term:`operator` roles
|
||||
- a :term:`configuration`
|
||||
|
|
@ -1,162 +1,5 @@
|
|||
Tutorial: a real world sample
|
||||
==============================
|
||||
|
||||
.. demo:: Demonstration : configuring (the setting of) your favorite web browser
|
||||
|
||||
This tutorial shows to you an example of Rougail use on
|
||||
how to set a proxy in the `Mozilla Firefox <https://www.mozilla.org/en-US/firefox/new/>`_ browser.
|
||||
|
||||
More precisely, this tutorial aims at reproducing this Mozilla Firefox settings page:
|
||||
|
||||
.. image:: images/firefox.png
|
||||
|
||||
.. important:: Here we are in the configuration validation use case,
|
||||
that is the values entered by the user have to be validated.
|
||||
It's a common use case, but not the only one.
|
||||
|
||||
Let's explain this use case.
|
||||
|
||||
The Firefox proxy configuration
|
||||
-------------------------------------------
|
||||
|
||||
The `proxy` family
|
||||
-------------------
|
||||
|
||||
Let's create our first :term:`dictionary`.
|
||||
|
||||
.. prerequisites:: Let's create a folder named `dict` and a dictionary file inside
|
||||
|
||||
We will put our dictionary files in this folder.
|
||||
|
||||
Then let's put our first dictionary file in this folder, named :file:`00-proxy.yml`
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`00-proxy.yml` file
|
||||
:linenos:
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
proxy:
|
||||
description: Proxy configuration in order to have access to the internet
|
||||
type: family
|
||||
|
||||
We can see that we have defined a :term:`family` here, and this family is *empty*
|
||||
(that is, the family container contains no variable yet).
|
||||
|
||||
.. admonition:: If a family is empty
|
||||
|
||||
We need to specify the :term:`family` type (line 5) here because if we don't,
|
||||
the Rougail's type engine will infer it by default as a :term:`variable`.
|
||||
|
||||
It's because we don't have set any :term:`variable` inside.
|
||||
|
||||
|
||||
.. note:: The variables will be created in several files for educational purposes.
|
||||
Obviously all the variables can be put in the same file.
|
||||
|
||||
|
||||
The proxy's configuration type
|
||||
----------------------------------
|
||||
|
||||
In the Firefox configuration, it is possible to define several configuration modes,
|
||||
from no proxy at all (`no proxy`) to a kind of automatic configuration mode from a file (`set up proxy configuration from a file`).
|
||||
|
||||
We're gonna create a first variable in this family with "Proxy mode" as the description.
|
||||
Let's create a second :file:`dict/01-proxy_mode.yml` file.
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the :file:`001-proxy_mode.yml` file
|
||||
:linenos:
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
proxy:
|
||||
proxy_mode:
|
||||
description: Proxy mode
|
||||
type: choice
|
||||
choices:
|
||||
- No proxy
|
||||
- Auto-detect proxy settings for this network
|
||||
- Use system proxy settings
|
||||
- Manual proxy configuration
|
||||
- Automatic proxy configuration URL
|
||||
default: No proxy
|
||||
|
||||
The `proxy_mode` variable requires a value (that is, `None` is not an option).
|
||||
It shall have a value, but what if the user *does not* specify any value?
|
||||
There is line 13, a possibility of setting a default value, wich is `No proxy` as the default.
|
||||
|
||||
The `proxy_mode` setting is "choice" (`type: choice`) means that
|
||||
there is a list of available values that can be selected.
|
||||
We say that the `proxy_mode` variable is *constrained* (by choices).
|
||||
|
||||
Line 8 to 12, we have the list of the possible (authorized) values:
|
||||
|
||||
- No proxy
|
||||
- Auto-detect proxy settings for this network
|
||||
- Use system proxy settings
|
||||
- Manual proxy configuration
|
||||
- Automatic proxy configuration URL
|
||||
|
||||
Now let's test our first two dictionaries:
|
||||
|
||||
|
||||
>>> from rougail import Rougail, RougailConfig
|
||||
>>> from pprint import pprint
|
||||
>>> RougailConfig['dictionaries_dir'] = ['dict']
|
||||
>>> rougail = Rougail()
|
||||
>>> config = rougail.get_config()
|
||||
>>> config.property.read_only()
|
||||
>>> pprint(config.value.get(), sort_dicts=False)
|
||||
{'rougail.proxy.proxy_mode': 'No proxy'}
|
||||
|
||||
The manual mode
|
||||
------------------
|
||||
|
||||
.. questions:: OK then. What happens when you select the "Manual proxy configuration"?
|
||||
|
||||
A good configuration design is to place all the proxy's manual configuration in a :term:`family`.
|
||||
Let's create the :file:`dict/02-proxy_manual.yml` dictionary:
|
||||
|
||||
.. code-block:: yaml
|
||||
:caption: the the :file:`dict/02-proxy_manual.yml` file
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
proxy:
|
||||
manual:
|
||||
description: Manual proxy configuration
|
||||
type: family
|
||||
disabled:
|
||||
type: jinja
|
||||
jinja: |
|
||||
{% if rougail.proxy.proxy_mode != 'Manual proxy configuration' %}
|
||||
the proxy mode is not manual
|
||||
{% endif %}
|
||||
|
||||
Well, if the user selects the "Manual proxy configuration" proxy mode, we want to see a new subfamily (that is, a new set of configuration variables) called `manual` to appear (which is disabled).
|
||||
|
||||
.. glossary::
|
||||
|
||||
subfamily
|
||||
|
||||
A subfamily is just a family inside a family, a family that contains a family.
|
||||
|
||||
.. questions:: What about this `Jinja` type?
|
||||
|
||||
If the :term:`Jinja` template returns some text, then the family will be `disabled`. Otherwise it is accessible.
|
||||
Deactivating a family means that we will not be able to access it as well as the variables or families included in this family.
|
||||
|
||||
.. note:: If the Jinja template does not return any text, the variable will be **enabled**.
|
||||
Here we are using the Jinja condition statement.
|
||||
|
||||
.. glossary::
|
||||
|
||||
Jinja
|
||||
|
||||
`Jinja <https://jinja.palletsprojects.com>`_ is a template engine.
|
||||
we are using Jinja in a classical way, that is, Jinja allows us to handle different cases,
|
||||
for example with the `if` statement.
|
||||
:orphan:
|
||||
|
||||
The HTTP proxy configuration
|
||||
------------------------------
|
||||
|
|
@ -170,7 +13,7 @@ Let's create the :file:`dict/03-proxy_manual_http_proxy.yml` dictionary:
|
|||
:linenos:
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
proxy:
|
||||
manual:
|
||||
http_proxy:
|
||||
|
|
@ -195,7 +38,7 @@ We then want to offer the user the possibility of providing the same proxy for t
|
|||
.. code-block:: yaml
|
||||
:caption: the :file:`dict/04-proxy_manual_http_use_for_https.yml` file
|
||||
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
proxy:
|
||||
manual:
|
||||
use_for_https:
|
||||
|
|
@ -216,7 +59,7 @@ Let's create the :file:`dict/05-proxy_manual_ssl_proxy.yml` file:
|
|||
:linenos:
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
proxy:
|
||||
manual:
|
||||
ssl_proxy:
|
||||
|
|
@ -271,9 +114,12 @@ Let's look at what happens if we try to access the `rougail.proxy.manual` variab
|
|||
We have an error (with the message defined in the Jinja template):
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
.. code-block:: shell
|
||||
|
||||
tiramisu.error.PropertiesOptionError: cannot access to
|
||||
optiondescription "Manual proxy configuration" because
|
||||
has property "disabled" (the mode proxy is not manual)
|
||||
|
||||
tiramisu.error.PropertiesOptionError: cannot access to optiondescription "Manual proxy configuration" because has property "disabled" (the mode proxy is not manual)
|
||||
|
||||
Let's configure the proxy in manual mode
|
||||
|
||||
|
|
@ -291,7 +137,7 @@ We can see that the returned variables does have the desired values:
|
|||
'rougail.proxy.manual.http_proxy.port': '8080',
|
||||
'rougail.proxy.manual.use_for_https': True}
|
||||
|
||||
Let's set the `read_only` mode:
|
||||
Let's set the `read_only` mode and have a look at the configuration again:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
@ -307,7 +153,7 @@ Let's set the `read_only` mode:
|
|||
In the `read_only` mode, we can see that the HTTPS configuration appears.
|
||||
|
||||
.. note:: We can see that `rougail.proxy.manual.http_proxy` values have been copied
|
||||
in `rougail.proxy.manual.ssl_proxy` too...
|
||||
in `rougail.proxy.manual.ssl_proxy` too.
|
||||
|
||||
Changing values programmatically
|
||||
--------------------------------------
|
||||
|
|
@ -357,7 +203,7 @@ Let's create the :file:`dict/06-proxy_manual_socks_proxy.yml` file:
|
|||
:caption: the :file:`dict/06-proxy_manual_socks_proxy.yml` file
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
proxy:
|
||||
manual:
|
||||
socks_proxy:
|
||||
|
|
@ -389,7 +235,7 @@ Let's create the :file:`dict/07-proxy_auto.yml` file:
|
|||
:caption: the :file:`dict/07-proxy_auto.yml` file
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
proxy:
|
||||
auto:
|
||||
type: web_address
|
||||
|
|
@ -416,7 +262,7 @@ Let's create the :file:`dict/07-proxy_no_proxy.yml` file:
|
|||
:linenos:
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
proxy:
|
||||
no_proxy:
|
||||
description: Address for which proxy will be desactivated
|
||||
|
|
@ -507,7 +353,7 @@ Nothing special when creating the authentication request. To do this, let's crea
|
|||
:linenos:
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
proxy:
|
||||
prompt_authentication:
|
||||
description: Prompt for authentication if password is saved
|
||||
|
|
@ -532,7 +378,7 @@ Let's create a `dict/09-proxy_proxy_dns_socks5.yml` file:
|
|||
:linenos:
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
proxy:
|
||||
proxy_dns_socks5:
|
||||
description: Use proxy DNS when using SOCKS v5
|
||||
|
|
@ -575,7 +421,7 @@ Let's create a `dict/10-proxy_dns_over_https.yml` file:
|
|||
:linenos:
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
proxy:
|
||||
dns_over_https:
|
||||
description: DNS over HTTPS
|
||||
|
|
@ -648,7 +494,7 @@ Here is the complete content of the FoxyProxy type proxy configuration
|
|||
:linenos:
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
proxy:
|
||||
_type: leadership
|
||||
title:
|
||||
|
|
@ -810,7 +656,7 @@ If you prefer this option, here is a second extra dictionary :file:`foxyproxy/01
|
|||
:linenos:
|
||||
|
||||
---
|
||||
version: '1.0'
|
||||
version: '1.1'
|
||||
proxy:
|
||||
username:
|
||||
redefine: true
|
||||
3
docs/user_datas/bitwarden.rst
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Load user datas from Bitwarden server
|
||||
=====================================
|
||||
|
||||
3
docs/user_datas/commandline.rst
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Load user datas from commandline parser
|
||||
=======================================
|
||||
|
||||
3
docs/user_datas/environment.rst
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Load user datas from a environment variable
|
||||
===========================================
|
||||
|
||||
21
docs/user_datas/index.rst
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
`Rougail`'s user datas description
|
||||
==================================
|
||||
|
||||
Rougail is a collections of subproject to adjust functionalities to your needs.
|
||||
|
||||
User datas is one of category of subjects. The goal is to setup variable with value define by user.
|
||||
|
||||
There is differents user datas types:
|
||||
|
||||
.. toctree::
|
||||
:titlesonly:
|
||||
:caption: Use library
|
||||
|
||||
yaml
|
||||
environment
|
||||
commandline
|
||||
bitwarden
|
||||
questionary
|
||||
|
||||
.. ansible
|
||||
|
||||
3
docs/user_datas/questionary.rst
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Load user datas from a command line interface
|
||||
=============================================
|
||||
|
||||
3
docs/user_datas/yaml.rst
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Load user datas from a YAML file
|
||||
================================
|
||||
|
||||
|
|
@ -1,67 +1,158 @@
|
|||
The variables
|
||||
===================
|
||||
==============
|
||||
|
||||
Synopsis
|
||||
------------
|
||||
---------
|
||||
|
||||
.. glossary::
|
||||
|
||||
variable
|
||||
variables
|
||||
|
||||
A variable is an abstract black box (container) paired with an associated symbolic name, which contains some defined or undefined quantity of data referred to as a `value`.
|
||||
A variable is an abstract black box (container) paired with an associated symbolic name, most often an option configuration, hich contains some defined or undefined data setting referred to as a :term:`value`.
|
||||
|
||||
value
|
||||
|
||||
A value is a variable's setting.
|
||||
Variable can have a default value, that is a setting defined in the :term:`structure file`,
|
||||
or no value at all, then the value needs to be define later by the :term:`operator`.
|
||||
|
||||
.. discussion:: Discussion
|
||||
|
||||
The variable is, by definition, strongly typed.
|
||||
Rougail uses static type definition and even type inference.
|
||||
Indeed, the constistency handling system heavyly relies on the type system definition.
|
||||
Variables may only be able to store a specified data type.
|
||||
OK, variables are the containers for storing the values. It has something to do with typing.
|
||||
|
||||
But consitency handling system is is not just about strong typing. It is more than that.
|
||||
|
||||
Names
|
||||
------
|
||||
|
||||
Variable name
|
||||
|
||||
Variable's associated symbolic name.
|
||||
|
||||
.. seealso::
|
||||
|
||||
Have a look at the :ref:`convention on variable naming link <convention on variable names>`.
|
||||
|
||||
Variable's types
|
||||
-----------------
|
||||
|
||||
.. type-along:: type inference
|
||||
|
||||
If the `type` attribute is not set, Rougail infers a `string` type for the `proxy_mode` configuration option variable type as defined in the structure file.
|
||||
|
||||
.. type-along:: integer type
|
||||
|
||||
If the operator sets an option value for example with the `integer` type, like this:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
---
|
||||
example_var:
|
||||
description: This is an example variable
|
||||
type: integer
|
||||
|
||||
Then Rougail will expect a `int` as a value for the `example_var` variable.
|
||||
|
||||
.. type-along:: the choice type
|
||||
|
||||
.. glossary::
|
||||
|
||||
choice type
|
||||
|
||||
A choice type variable is a variable where the content is constrained by a list
|
||||
|
||||
When a variable's setting is "choice" (`type: choice`), it means that
|
||||
there is a list of available values that can be selected.
|
||||
|
||||
Shorthand declaration
|
||||
----------------------------
|
||||
|
||||
Shorthand declaration is a way to declare a variable in a single line. But you can only define variable name, description, multi or default value.
|
||||
|
||||
.. glossary::
|
||||
|
||||
short-hand notation
|
||||
|
||||
A short-hand notation in Rougail is the ability to define a variable in
|
||||
a short-hand way, there are several example:
|
||||
|
||||
.. discussion:: This definition, makes a heavy use of data typing.
|
||||
Indeed, depending on the type system definition of the constistency handling system used, variables may only be able to store a specified data type.
|
||||
OK, variables are the containers for storing the values. It has something to do with typing.
|
||||
But this is not just about typing.
|
||||
- a default value:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
my_var: true
|
||||
|
||||
instead of:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
my_var:
|
||||
default: true
|
||||
|
||||
Name
|
||||
-------------
|
||||
|
||||
Variable's associated symbolic name.
|
||||
|
||||
It's best to follow the :ref:`convention on variable names`.
|
||||
To create a variable, just add a key with it's name and default value as value.
|
||||
Be careful not to declare any other attributes.
|
||||
|
||||
To declare a multi variable just add a list as default value.
|
||||
|
||||
By default, the description of the variable is the variable name.
|
||||
If you add a comment in the same line of the name, this comment will be used has a description.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
%YAML 1.2
|
||||
---
|
||||
version: 1.1
|
||||
|
||||
my_variable: 1 # This is a great integer variable
|
||||
|
||||
my_multi_variable: # This is a great multi string variable
|
||||
- value1
|
||||
- value2
|
||||
...
|
||||
|
||||
Parameters
|
||||
-------------
|
||||
|
||||
.. list-table::
|
||||
.. list-table::
|
||||
:widths: 15 45
|
||||
:header-rows: 1
|
||||
|
||||
|
||||
* - Parameter
|
||||
- Comments
|
||||
|
||||
|
||||
* - **help**
|
||||
|
||||
`string`
|
||||
`string`
|
||||
- Additional help associated with the variable.
|
||||
|
||||
* - **default**
|
||||
|
||||
* - **default**
|
||||
- Default value(s) of the variable.
|
||||
|
||||
This value is typed, you must correctly fill out the YAML file to avoid defining a value with an incorrect type. For example, a `number` must be a digit type, a multiple variable must be a `list` type, ...
|
||||
|
||||
|
||||
This value is typed, you must correctly fill out the YAML file to avoid defining a value with an incorrect type. For example, a `integer` must be a digit type, a multiple variable must be a `list` type, ...
|
||||
|
||||
For a non :term:`leading` multiple variable, the first value defined in the list will also be the default value proposed if a new value is added to this variable.
|
||||
|
||||
* - **validators**
|
||||
|
||||
`list`
|
||||
- Value validators.
|
||||
|
||||
Jinja template list. The value of the variable will be considered invalid if the template has a return value.
|
||||
|
||||
Jinja template list. The value of the variable will be considered invalid if the template has a return value.
|
||||
* - **auto_save**
|
||||
|
||||
`boolean`
|
||||
`boolean`
|
||||
- Variable with automatically modified value.
|
||||
|
||||
|
||||
A variable with automatically modified value is a variable whose value will be considered as *modified* (that is, it is no longer the variable's default value).
|
||||
|
||||
For example, if the value of this variable comes from a calculation, the value will no longer be recalculated.
|
||||
|
||||
These variables are usually :term:`required` variables. In fact, these variables are only automatically modified if they have a value.
|
||||
|
||||
|
||||
A :term:`leader` or :term:`follower` variable cannot have the `auto_save` property.
|
||||
|
||||
**Default value**: `false`
|
||||
|
|
@ -69,11 +160,11 @@ Parameters
|
|||
|
||||
`string`
|
||||
- Variable's mode.
|
||||
|
||||
|
||||
**Default value**: The `default` mode of a variable is the mode of the parent family.
|
||||
|
||||
Special cases :
|
||||
|
||||
|
||||
- a variable with an automatically modified value or an automatic read-only variable is by default in `basic` mode
|
||||
- if the variable is not in a family, the variable will have a `standard` mode by default
|
||||
- a :term:`mandatory` variable without default value (calculate or not) will have a `basic` mode
|
||||
|
|
@ -83,6 +174,12 @@ Parameters
|
|||
- The value of the variable is a list.
|
||||
|
||||
**Default value**: `false`
|
||||
|
||||
**Parameters**:
|
||||
|
||||
- multi_length: number of expected values for a multiple variable
|
||||
- multi_min_length: maximum number of expected values for a multiple variable
|
||||
- multi_max_length: minimum number of expected values for a minimum variable
|
||||
* - **unique**
|
||||
|
||||
`boolean`
|
||||
|
|
@ -91,19 +188,19 @@ Parameters
|
|||
**Default value**: `false`
|
||||
* - **hidden**
|
||||
|
||||
`boolean` or :term:`calculation`
|
||||
`boolean` or :term:`calculation`
|
||||
- Invisible variable.
|
||||
|
||||
|
||||
Enables us to *hide* a variable.
|
||||
|
||||
This means that the variable will no longer be visible in `read-write` mode, but only for calculations or in `read-only` mode.
|
||||
|
||||
|
||||
When a variable is made invisible, the user will not be able to modify its value; if he has already succeeded in modifying it, this value will not be taken into account.
|
||||
|
||||
**Default value**: `false`
|
||||
* - **disabled**
|
||||
|
||||
`boolean` or :term:`calculation`
|
||||
`boolean` or :term:`calculation`
|
||||
- Disabled variable.
|
||||
|
||||
Allows us to deactivate a variable.
|
||||
|
|
@ -113,7 +210,7 @@ Parameters
|
|||
**Default value**: `false`.
|
||||
* - **mandatory**
|
||||
|
||||
`boolean` or :term:`calculation`
|
||||
`boolean` or :term:`calculation`
|
||||
- Mandatory variable.
|
||||
|
||||
Variable whose value is `required`.
|
||||
|
|
@ -124,7 +221,7 @@ Parameters
|
|||
* - **redefine**
|
||||
|
||||
`boolean`
|
||||
- It is possible to define a variable in one :term:`dictionary` and change its behavior in a second :term:`dictionary`. In this case you must explicitly redefine the variable.
|
||||
- It is possible to define a variable in one :term:`structure file` and change its behavior in a second :term:`structure file`. In this case you must explicitly redefine the variable.
|
||||
|
||||
**Default value**: `false`
|
||||
* - **exists**
|
||||
|
|
@ -132,76 +229,76 @@ Parameters
|
|||
`boolean`
|
||||
- This attribute does two things:
|
||||
|
||||
- creates a variable if it does not exist in another :term:`dictionary` (otherwise do nothing), in this case the value of the attribute must be `true`
|
||||
- creates a variable if it does not exist in another :term:`structure file` (otherwise do nothing), in this case the value of the attribute must be `true`
|
||||
- in conjunction with the `redefine` attribute set to `true`, only modifies the behavior if it is pre-existing, in which case the attribute's value must be `false`.
|
||||
|
||||
|
||||
**Default value**: `null`
|
||||
* - **test**
|
||||
|
||||
`list`
|
||||
- The `test` attribute is a special attribute that allows :term:`dictionary` designers to influence a test robot by specifying useful values to test.
|
||||
|
||||
- The `test` attribute is a special attribute that allows :term:`structure file` designers to influence a test robot by specifying useful values to test.
|
||||
|
||||
Concretely, the content of this attribute is recorded in the `information` attribute of the corresponding `Tiramisu` option object.
|
||||
|
||||
Variables types
|
||||
----------------
|
||||
|
||||
A variable **has a type**.
|
||||
Variables type list
|
||||
--------------------
|
||||
|
||||
This type enables the variable to define the values that are accepted by this variable.
|
||||
A variable **always has a type**. The system is **strongly** typed.
|
||||
|
||||
.. list-table::
|
||||
Depending on the definition of the variable type, the defined variable will accept values of the associated type.
|
||||
|
||||
.. list-table::
|
||||
:widths: 15 25 20 15
|
||||
:header-rows: 1
|
||||
|
||||
|
||||
* - Value
|
||||
- Comments
|
||||
- Parameters
|
||||
- Samples
|
||||
|
||||
|
||||
* - string
|
||||
- character string (default type)
|
||||
-
|
||||
-
|
||||
- test
|
||||
|
||||
|
||||
"1"
|
||||
|
||||
"true"
|
||||
* - number
|
||||
- a number
|
||||
- `min_number`: minimum number allowed
|
||||
|
||||
`max_number`: maximum number allowed
|
||||
* - integer
|
||||
- a integer
|
||||
- `min_integer`: minimum integer allowed
|
||||
|
||||
`max_integer`: maximum integer allowed
|
||||
- 1
|
||||
* - float
|
||||
- a floating number
|
||||
-
|
||||
-
|
||||
- 1.2
|
||||
* - boolean
|
||||
- A boolean, if no value is defined the default value of this variable will be `true`, the variable will also be :term:`mandatory` by default
|
||||
-
|
||||
-
|
||||
- `true`
|
||||
|
||||
|
||||
`false`
|
||||
* - secret
|
||||
- a secret (like a password, a private key, etc.)
|
||||
-
|
||||
-
|
||||
- `hO_'hi`
|
||||
* - mail
|
||||
- a mail address
|
||||
-
|
||||
* - mail
|
||||
- a mail address
|
||||
-
|
||||
- test@rougail.example
|
||||
* - unix_filename
|
||||
- a file name in the Unix meaning
|
||||
-
|
||||
-
|
||||
- :file:`/etc/passwd`
|
||||
* - date
|
||||
* - date
|
||||
- a date in the format `%Y-%m-%d`
|
||||
-
|
||||
-
|
||||
- `2021-01-30`
|
||||
* - unix_user
|
||||
- a user in the Unix meaning
|
||||
-
|
||||
- a user in the Unix meaning
|
||||
-
|
||||
- test
|
||||
* - ip
|
||||
- any kind of IPv4 address
|
||||
|
|
@ -212,73 +309,73 @@ This type enables the variable to define the values that are accepted by this va
|
|||
* - cidr
|
||||
- any IPv4 address in the CIDR format
|
||||
- `private_only`: only private IPs (`false` by default)
|
||||
|
||||
`allow_reserved`: allows reserved IPs (`false` by default)
|
||||
|
||||
`allow_reserved`: allows reserved IPs (`false` by default)
|
||||
- `1.2.3.4/24`
|
||||
* - netmask
|
||||
- mask of an IPv4 address
|
||||
-
|
||||
-
|
||||
- `255.255.255.0`
|
||||
* - network
|
||||
- network address
|
||||
-
|
||||
-
|
||||
- `192.168.1.0`
|
||||
* - network_cidr
|
||||
- network address in CIDR format
|
||||
-
|
||||
-
|
||||
- `192.168.1.0/24`
|
||||
* - broadcast
|
||||
- broadcast address
|
||||
-
|
||||
-
|
||||
- `255.255.255.255`
|
||||
* - netbios
|
||||
- netbios name
|
||||
-
|
||||
- netbios name
|
||||
-
|
||||
- machine
|
||||
* - domainname
|
||||
- domain name
|
||||
- domain name
|
||||
- `allow_ip`: allows an IP rather than a domain name (`false` by default)
|
||||
|
||||
|
||||
`allow_cidr_network`: allows a CIDR type network address (`false` by default)
|
||||
|
||||
|
||||
`allow_without_dot`: allows names without a dot (`false` by default)
|
||||
|
||||
|
||||
`allow_startswith_dot`: allows starting with a point (`false` by default)
|
||||
- `rougail.example`
|
||||
* - hostname
|
||||
- host name
|
||||
* - hostname
|
||||
- host name
|
||||
- `allow_ip`: allows an IP rather than a domain name (`false` by default)
|
||||
- machine
|
||||
* - web_address
|
||||
- web address
|
||||
- `allow_ip`: allows an IP rather than a domain name (`false` by default)
|
||||
|
||||
|
||||
`allow_without_dot`: allows names without a dot (`true` by default)
|
||||
- http://rougail.example
|
||||
* - port
|
||||
- port
|
||||
* - port
|
||||
- port
|
||||
- `allow_range`: allows a port range, for example 80:85 (`false` by default)
|
||||
|
||||
`allow_zero`: allows port 0 (false by default)
|
||||
|
||||
`allow_wellknown`: allows ports from 1 to 1023 (`true` by default)
|
||||
|
||||
|
||||
`allow_registred`: allows ports from 1024 to 49151 (`true` by default)
|
||||
|
||||
|
||||
`allow_private`: allows ports greater than 49152 (`true` by default)
|
||||
|
||||
|
||||
`allow_protocol`: allows the addition of the protocol, for example tcp:80 (`false` by default)
|
||||
|
||||
|
||||
- 8080
|
||||
* - mac
|
||||
- MAC address
|
||||
-
|
||||
-
|
||||
- 11:11:11:11:11:11
|
||||
* - unix_permissions
|
||||
- access rights to the file, directory, etc.
|
||||
-
|
||||
-
|
||||
- 644
|
||||
* - choice
|
||||
- choice variable
|
||||
-
|
||||
-
|
||||
* - choice
|
||||
- choice variable
|
||||
-
|
||||
-
|
||||
|
|
|
|||
|
|
@ -23,13 +23,17 @@ classifiers = [
|
|||
|
||||
]
|
||||
dependencies = [
|
||||
"pyyaml ~= 6.0.1",
|
||||
"ruamel.yaml ~= 0.17.40",
|
||||
"pydantic ~= 2.5.2",
|
||||
"jinja2 ~= 3.1.2",
|
||||
"tiramisu ~= 4.1.0"
|
||||
]
|
||||
[project.optional-dependancies]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pylint ~= 3.0.3",
|
||||
"pytest ~= 8.2.2",
|
||||
"lxml ~= 5.2.2"
|
||||
]
|
||||
|
||||
[tool.commitizen]
|
||||
|
|
|
|||
|
|
@ -28,19 +28,27 @@ along with this program; if not, write to the Free Software
|
|||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
"""
|
||||
from tiramisu import Config
|
||||
from copy import copy
|
||||
from tiramisu.error import PropertiesOptionError
|
||||
from warnings import warn
|
||||
from typing import List
|
||||
|
||||
from .convert import RougailConvert
|
||||
from .config import RougailConfig
|
||||
from .update import RougailUpgrade
|
||||
from .object_model import CONVERT_OPTION
|
||||
from .utils import normalize_family
|
||||
|
||||
|
||||
def tiramisu_display_name(kls) -> str:
|
||||
def tiramisu_display_name(kls, subconfig) -> str:
|
||||
"""Replace the Tiramisu display_name function to display path + description"""
|
||||
doc = kls.impl_get_information("doc", None)
|
||||
doc = kls._get_information(subconfig, "doc", None)
|
||||
comment = f" ({doc})" if doc and doc != kls.impl_getname() else ""
|
||||
return f"{kls.impl_getpath()}{comment}"
|
||||
if "{{ suffix }}" in comment:
|
||||
comment = comment.replace('{{ suffix }}', str(subconfig.suffixes[-1]))
|
||||
path = kls.impl_getpath()
|
||||
if "{{ suffix }}" in path:
|
||||
path = path.replace('{{ suffix }}', normalize_family(str(subconfig.suffixes[-1])))
|
||||
return f"{path}{comment}"
|
||||
|
||||
|
||||
class Rougail:
|
||||
|
|
@ -61,9 +69,10 @@ class Rougail:
|
|||
path_prefix: str,
|
||||
) -> None:
|
||||
"""Add a prefix"""
|
||||
self.converted.load_config()
|
||||
self.converted.parse_directories(path_prefix)
|
||||
|
||||
def get_config(self):
|
||||
def run(self):
|
||||
"""Get Tiramisu Config"""
|
||||
if not self.config:
|
||||
tiram_obj = self.converted.save(self.rougailconfig["tiramisu_cache"])
|
||||
|
|
@ -77,5 +86,47 @@ class Rougail:
|
|||
self.config.property.read_write()
|
||||
return self.config
|
||||
|
||||
def get_config(self):
|
||||
warn("get_config is deprecated, use run instead", DeprecationWarning, stacklevel=2)
|
||||
return self.run()
|
||||
|
||||
__ALL__ = ("Rougail", "RougailConfig", "RougailUpgrade")
|
||||
def user_datas(self,
|
||||
user_datas: List[dict]):
|
||||
values = {}
|
||||
errors = []
|
||||
warnings = []
|
||||
for datas in user_datas:
|
||||
for name, data in datas.get('values', {}).items():
|
||||
values.setdefault(name, {}).update(data)
|
||||
errors.extend(datas.get('errors', []))
|
||||
warnings.extend(datas.get('warnings', []))
|
||||
while values:
|
||||
value_is_set = False
|
||||
for option in self.config:
|
||||
if option.path() in values and option.index() in values[option.path()]:
|
||||
try:
|
||||
option.value.set(values[option.path()])
|
||||
value_is_set = True
|
||||
values.pop(option.path())
|
||||
except:
|
||||
pass
|
||||
if not value_is_set:
|
||||
break
|
||||
for path, data in values.items():
|
||||
for index, value in data.items():
|
||||
try:
|
||||
print('attention', path, value)
|
||||
self.config.option(path).value.set(value)
|
||||
print('pfff')
|
||||
except AttributeError as err:
|
||||
errors.append(str(err))
|
||||
except ValueError as err:
|
||||
errors.append(str(err).replace('"', "'"))
|
||||
except PropertiesOptionError as err:
|
||||
# warnings.append(f'"{err}" but is defined in "{self.filename}"')
|
||||
warnings.append(str(err))
|
||||
return {'errors': errors,
|
||||
'warnings': warnings,
|
||||
}
|
||||
|
||||
__all__ = ("Rougail", "RougailConfig", "RougailUpgrade")
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ def get_annotators(annotators, module_name):
|
|||
path = str(pathobj)
|
||||
if not path.endswith(".py") or path.endswith("__.py"):
|
||||
continue
|
||||
module = load_modules(path)
|
||||
module = load_modules(module_name, path)
|
||||
if "Annotator" not in dir(module):
|
||||
continue
|
||||
annotators[module_name].append(module.Annotator)
|
||||
|
|
@ -62,21 +62,26 @@ class SpaceAnnotator: # pylint: disable=R0903
|
|||
if ANNOTATORS is None:
|
||||
ANNOTATORS = {}
|
||||
get_annotators(ANNOTATORS, "rougail.annotator")
|
||||
for extra_annotator in objectspace.rougailconfig["extra_annotators"]:
|
||||
for extra_annotator in objectspace.extra_annotators:
|
||||
if extra_annotator in ANNOTATORS:
|
||||
continue
|
||||
get_annotators(ANNOTATORS, extra_annotator)
|
||||
for plugin in objectspace.plugins:
|
||||
try:
|
||||
get_annotators(ANNOTATORS, f'rougail.{plugin}.annotator')
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
annotators = ANNOTATORS["rougail.annotator"].copy()
|
||||
for extra_annotator in objectspace.rougailconfig["extra_annotators"]:
|
||||
for extra_annotator in objectspace.extra_annotators:
|
||||
annotators.extend(ANNOTATORS[extra_annotator])
|
||||
for plugin in objectspace.plugins:
|
||||
annotators.extend(ANNOTATORS[f'rougail.{plugin}.annotator'])
|
||||
annotators = sorted(annotators, key=get_level)
|
||||
functions = {}
|
||||
functions_files = objectspace.rougailconfig["functions_file"]
|
||||
if not isinstance(functions_files, list):
|
||||
functions_files = [functions_files]
|
||||
functions_files = objectspace.functions_files
|
||||
for functions_file in functions_files:
|
||||
if isfile(functions_file):
|
||||
loaded_modules = load_modules(functions_file)
|
||||
loaded_modules = load_modules('function_file', functions_file)
|
||||
for function in dir(loaded_modules):
|
||||
if function.startswith("_"):
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ from typing import Optional
|
|||
from rougail.i18n import _
|
||||
from rougail.error import DictConsistencyError
|
||||
from rougail.annotator.variable import Walk
|
||||
from rougail.object_model import VariableCalculation
|
||||
|
||||
|
||||
class Mode: # pylint: disable=R0903
|
||||
|
|
@ -63,16 +64,30 @@ class Annotator(Walk):
|
|||
self.objectspace = objectspace
|
||||
if not self.objectspace.paths:
|
||||
return
|
||||
self.modes = {
|
||||
name: Mode(idx)
|
||||
for idx, name in enumerate(self.objectspace.rougailconfig["modes_level"])
|
||||
}
|
||||
self.check_leadership()
|
||||
self.remove_empty_families()
|
||||
self.family_names()
|
||||
self.change_modes()
|
||||
self.dynamic_families()
|
||||
if self.objectspace.modes_level:
|
||||
self.modes = {
|
||||
name: Mode(idx)
|
||||
for idx, name in enumerate(self.objectspace.modes_level)
|
||||
}
|
||||
self.default_variable_mode = self.objectspace.default_variable_mode
|
||||
self.default_family_mode = self.objectspace.default_family_mode
|
||||
self.change_modes()
|
||||
self.convert_help()
|
||||
|
||||
def check_leadership(self) -> None:
|
||||
"""No subfamily in a leadership"""
|
||||
for family in self.get_families():
|
||||
if family.type != "leadership":
|
||||
continue
|
||||
for variable_path in self.objectspace.parents[family.path]:
|
||||
variable = self.objectspace.paths[variable_path]
|
||||
if variable.type in self.objectspace.family_types:
|
||||
msg = f'the leadership "{family.path}" cannot have the { variable.type } "{ variable.path}"'
|
||||
raise DictConsistencyError(msg, 24, variable.xmlfiles)
|
||||
|
||||
def remove_empty_families(self) -> None:
|
||||
"""Remove all families without any variable"""
|
||||
removed_families = []
|
||||
|
|
@ -80,7 +95,7 @@ class Annotator(Walk):
|
|||
if isinstance(family, self.objectspace.family) and not self._has_variable(
|
||||
family.path
|
||||
):
|
||||
if "." in family.path:
|
||||
if self.objectspace.paths.default_namespace is None or "." in family.path:
|
||||
removed_families.append(family.path)
|
||||
removed_families.reverse()
|
||||
for family in removed_families:
|
||||
|
|
@ -104,20 +119,17 @@ class Annotator(Walk):
|
|||
if not family.description:
|
||||
family.description = family.name
|
||||
|
||||
# family.doc = family.description
|
||||
# del family.description
|
||||
|
||||
def change_modes(self):
|
||||
"""change the mode of variables"""
|
||||
modes_level = self.objectspace.rougailconfig["modes_level"]
|
||||
default_variable_mode = self.objectspace.rougailconfig["default_variable_mode"]
|
||||
modes_level = self.objectspace.modes_level
|
||||
default_variable_mode = self.default_variable_mode
|
||||
if default_variable_mode not in modes_level:
|
||||
msg = _(
|
||||
f'default variable mode "{default_variable_mode}" is not a valid mode, '
|
||||
f"valid modes are {modes_level}"
|
||||
)
|
||||
raise DictConsistencyError(msg, 72, None)
|
||||
default_family_mode = self.objectspace.rougailconfig["default_family_mode"]
|
||||
default_family_mode = self.default_family_mode
|
||||
if default_family_mode not in modes_level:
|
||||
msg = _(
|
||||
f'default family mode "{default_family_mode}" is not a valid mode, '
|
||||
|
|
@ -131,12 +143,21 @@ class Annotator(Walk):
|
|||
families.reverse()
|
||||
for family in families:
|
||||
self._change_family_mode(family)
|
||||
if self.objectspace.paths.default_namespace is None:
|
||||
for variable_path in self.objectspace.parents['.']:
|
||||
variable = self.objectspace.paths[variable_path]
|
||||
if variable.type == "symlink" or variable_path in self.objectspace.families:
|
||||
continue
|
||||
self._set_default_mode_variable(variable,
|
||||
self.default_variable_mode,
|
||||
check_level=False,
|
||||
)
|
||||
|
||||
def valid_mode(
|
||||
self,
|
||||
obj,
|
||||
) -> None:
|
||||
modes_level = self.objectspace.rougailconfig["modes_level"]
|
||||
modes_level = self.objectspace.modes_level
|
||||
if self._has_mode(obj) and obj.mode not in modes_level:
|
||||
msg = _(
|
||||
f'mode "{obj.mode}" for "{obj.name}" is not a valid mode, '
|
||||
|
|
@ -162,6 +183,7 @@ class Annotator(Walk):
|
|||
continue
|
||||
if leader is None and family.type == "leadership":
|
||||
leader = variable
|
||||
leader_mode = leader.mode
|
||||
if variable_path in self.objectspace.families:
|
||||
# set default mode a subfamily
|
||||
if family_mode and not self._has_mode(variable):
|
||||
|
|
@ -172,9 +194,9 @@ class Annotator(Walk):
|
|||
if leader:
|
||||
self._set_default_mode_leader(leader, variable)
|
||||
self._set_default_mode_variable(variable, family_mode)
|
||||
if leader:
|
||||
if leader and leader_mode is not None:
|
||||
# here because follower can change leader mode
|
||||
self._set_auto_mode(family, leader.mode)
|
||||
self._set_auto_mode(family, leader_mode)
|
||||
|
||||
def _has_mode(self, obj) -> bool:
|
||||
return obj.mode and not obj.path in self.mode_auto
|
||||
|
|
@ -183,11 +205,12 @@ class Annotator(Walk):
|
|||
self,
|
||||
variable: "self.objectspace.variable",
|
||||
family_mode: Optional[str],
|
||||
check_level: bool=True,
|
||||
) -> None:
|
||||
# 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:
|
||||
variable.mode = self.objectspace.rougailconfig["modes_level"][0]
|
||||
variable.mode = self.objectspace.modes_level[0]
|
||||
# mandatory variable without value is a basic variable
|
||||
elif (
|
||||
not self._has_mode(variable)
|
||||
|
|
@ -195,8 +218,8 @@ class Annotator(Walk):
|
|||
and variable.default is None
|
||||
and variable.path not in self.objectspace.default_multi
|
||||
):
|
||||
variable_mode = self.objectspace.rougailconfig["modes_level"][0]
|
||||
if family_mode and self.modes[variable_mode] < self.modes[family_mode]:
|
||||
variable_mode = self.objectspace.modes_level[0]
|
||||
if check_level and family_mode and self.modes[variable_mode] < self.modes[family_mode]:
|
||||
msg = _(
|
||||
f'the variable "{variable.name}" is mandatory so in "{variable_mode}" mode '
|
||||
f'but family has the higher family mode "{family_mode}"'
|
||||
|
|
@ -220,20 +243,17 @@ class Annotator(Walk):
|
|||
leader: "self.objectspace.variable",
|
||||
follower: "self.objectspace.variable",
|
||||
) -> None:
|
||||
if follower.auto_save is True:
|
||||
msg = _(f'leader/followers "{follower.name}" could not be auto_save')
|
||||
raise DictConsistencyError(msg, 29, follower.xmlfiles)
|
||||
if leader == follower:
|
||||
# it's a leader
|
||||
if not leader.mode:
|
||||
self._set_auto_mode(
|
||||
leader, self.objectspace.rougailconfig["default_variable_mode"]
|
||||
leader, self.default_variable_mode
|
||||
)
|
||||
return
|
||||
if self._has_mode(follower):
|
||||
follower_mode = follower.mode
|
||||
else:
|
||||
follower_mode = self.objectspace.rougailconfig["default_variable_mode"]
|
||||
follower_mode = self.default_variable_mode
|
||||
if self.modes[leader.mode] > self.modes[follower_mode]:
|
||||
if self._has_mode(follower) and not self._has_mode(leader):
|
||||
# if follower has mode but not the leader
|
||||
|
|
@ -255,20 +275,18 @@ class Annotator(Walk):
|
|||
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]
|
||||
family_mode = self.default_family_mode
|
||||
min_variable_mode = self.objectspace.modes_level[-1]
|
||||
# change variable mode, but not if variables are not in a family
|
||||
is_leadership = family.type == "leadership"
|
||||
if family.path in self.objectspace.parents:
|
||||
for idx, variable_path in enumerate(self.objectspace.parents[family.path]):
|
||||
for variable_path in 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"
|
||||
]
|
||||
variable.mode = self.default_family_mode
|
||||
else:
|
||||
self._change_variable_mode(variable, family_mode, is_leadership)
|
||||
if self.modes[min_variable_mode] > self.modes[variable.mode]:
|
||||
|
|
@ -276,6 +294,10 @@ class Annotator(Walk):
|
|||
if not family.mode:
|
||||
# set the lower variable mode to family
|
||||
self._set_auto_mode(family, min_variable_mode)
|
||||
if self.modes[family.mode] < self.modes[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)
|
||||
|
||||
def _change_variable_mode(
|
||||
self,
|
||||
|
|
@ -286,7 +308,7 @@ class Annotator(Walk):
|
|||
if variable.mode:
|
||||
variable_mode = variable.mode
|
||||
else:
|
||||
variable_mode = self.objectspace.rougailconfig["default_variable_mode"]
|
||||
variable_mode = self.default_variable_mode
|
||||
# none basic variable in high level family has to be in high level
|
||||
if not is_follower and self.modes[variable_mode] < self.modes[family_mode]:
|
||||
if self._has_mode(variable):
|
||||
|
|
@ -299,33 +321,6 @@ class Annotator(Walk):
|
|||
if not variable.mode:
|
||||
variable.mode = variable_mode
|
||||
|
||||
def dynamic_families(self):
|
||||
"""link dynamic families to object"""
|
||||
for family in self.get_families():
|
||||
if family.type != "dynamic":
|
||||
continue
|
||||
try:
|
||||
family.variable = self.objectspace.paths[family.variable]
|
||||
except AttributeError as err:
|
||||
raise Exception(
|
||||
f'cannot load the dynamic family "{family.path}", cannot find variable "{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 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)
|
||||
|
||||
def convert_help(self):
|
||||
"""Convert variable help"""
|
||||
for family in self.get_families():
|
||||
|
|
|
|||
|
|
@ -112,37 +112,37 @@ class Annotator(Walk):
|
|||
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]
|
||||
self.frozen[path] = True
|
||||
elif self.frozen.get(path) is not True:
|
||||
self.frozen.setdefault(path, []).append(variable.hidden)
|
||||
if path in self.frozen:
|
||||
frozen = self.frozen[path]
|
||||
if frozen is True:
|
||||
value = True
|
||||
else:
|
||||
value = []
|
||||
for calculation in frozen:
|
||||
calculation_object = calculation.__class__
|
||||
calculation_dict = calculation.model_dump().copy()
|
||||
calculation_dict["attribute_name"] = "frozen"
|
||||
calculation_dict["path"] = variable.path
|
||||
value.append(calculation_object(**calculation_dict))
|
||||
calculation_copy = calculation.copy()
|
||||
calculation_copy.attribute_name = 'frozen'
|
||||
calculation_copy.ori_path = calculation_copy.path
|
||||
calculation_copy.path = path
|
||||
value.append(calculation_copy)
|
||||
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:
|
||||
if not variable.empty and self.objectspace.multis.get(variable.path, False):
|
||||
# a multi could not have "None" has value
|
||||
# to permit it, just add mandatory="False"
|
||||
# to permit it, just add empty="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)
|
||||
self.objectspace.properties.add(path, "force_store_value", True)
|
||||
|
||||
def _convert_property(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -48,13 +48,15 @@ class Annotator(Walk): # pylint: disable=R0903
|
|||
return
|
||||
self.objectspace = objectspace
|
||||
self.convert_value()
|
||||
self.add_choice_nil()
|
||||
self.valid_choices()
|
||||
|
||||
def convert_value(self) -> None:
|
||||
"""convert value"""
|
||||
for variable in self.get_variables():
|
||||
if variable.type == "symlink":
|
||||
continue
|
||||
if variable.version != '1.0' and variable.type == 'port':
|
||||
self._convert_port(variable)
|
||||
self._convert_value(variable)
|
||||
|
||||
def _convert_value(
|
||||
|
|
@ -66,58 +68,62 @@ class Annotator(Walk): # pylint: disable=R0903
|
|||
if variable.type == "boolean" and multi is False and variable.default is None:
|
||||
variable.default = True
|
||||
|
||||
if variable.default is None:
|
||||
if variable.default is None or isinstance(variable.default, Calculation):
|
||||
return
|
||||
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.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:
|
||||
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 variable.path in self.objectspace.followers:
|
||||
self.objectspace.default_multi[variable.path] = variable.default
|
||||
variable.default = None
|
||||
has_value = True
|
||||
|
||||
def add_choice_nil(self) -> None:
|
||||
if isinstance(variable.default, list):
|
||||
if not multi:
|
||||
msg = f'The variable "{variable.path}" with a list as default value must have "multi" attribute'
|
||||
raise DictConsistencyError(msg, 68, variable.xmlfiles)
|
||||
if variable.path in self.objectspace.followers and multi != "submulti":
|
||||
msg = _(
|
||||
f'the follower "{variable.name}" without multi attribute can only have one value'
|
||||
)
|
||||
raise DictConsistencyError(msg, 87, variable.xmlfiles)
|
||||
if not variable.default:
|
||||
variable.default = None
|
||||
else:
|
||||
if variable.path not in self.objectspace.leaders:
|
||||
if multi == "submulti":
|
||||
self.objectspace.default_multi[
|
||||
variable.path
|
||||
] = variable.default
|
||||
variable.default = None
|
||||
else:
|
||||
self.objectspace.default_multi[variable.path] = variable.default[
|
||||
0
|
||||
]
|
||||
elif variable.multi:
|
||||
msg = _(f'the variable "{variable.name}" is multi but has a non list default value')
|
||||
raise DictConsistencyError(msg, 12, variable.xmlfiles)
|
||||
elif variable.path in self.objectspace.followers:
|
||||
self.objectspace.default_multi[variable.path] = variable.default
|
||||
variable.default = None
|
||||
|
||||
def _convert_port(self, variable) -> None:
|
||||
if variable.multi is False and isinstance(variable.default, int):
|
||||
variable.default = str(variable.default)
|
||||
elif variable.multi is True and isinstance(variable.default, list):
|
||||
for idx, value in enumerate(variable.default):
|
||||
if isinstance(value, int):
|
||||
variable.default[idx] = str(value)
|
||||
|
||||
def valid_choices(self) -> None:
|
||||
"""A variable with type "Choice" that is not mandatory must has "nil" value"""
|
||||
for variable in self.get_variables():
|
||||
if variable.type != "choice":
|
||||
continue
|
||||
is_none = False
|
||||
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:
|
||||
variable.choices.append(None)
|
||||
if variable.choices is None:
|
||||
msg = f'the variable "{variable.path}" is a "choice" variable but don\'t have any choice'
|
||||
raise DictConsistencyError(msg, 19, variable.xmlfiles)
|
||||
if not variable.mandatory and not variable.multi:
|
||||
self.add_choice_nil(variable)
|
||||
|
||||
def add_choice_nil(self, variable) -> None:
|
||||
"""A variable with type "Choice" that is not mandatory must has "nil" value"""
|
||||
for choice in variable.choices:
|
||||
if choice is None:
|
||||
return
|
||||
variable.choices.append(None)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|||
|
||||
from rougail.i18n import _
|
||||
from rougail.error import DictConsistencyError
|
||||
from rougail.object_model import Calculation
|
||||
from rougail.object_model import Calculation, VariableCalculation
|
||||
from tiramisu.error import display_list
|
||||
|
||||
|
||||
class Walk:
|
||||
|
|
@ -64,23 +65,92 @@ class Annotator(Walk): # pylint: disable=R0903
|
|||
if not objectspace.paths:
|
||||
return
|
||||
self.objectspace = objectspace
|
||||
self.forbidden_name = [
|
||||
"services",
|
||||
self.objectspace.rougailconfig["variable_namespace"],
|
||||
]
|
||||
for extra in self.objectspace.rougailconfig["extra_dictionaries"]:
|
||||
self.forbidden_name.append(extra)
|
||||
if self.objectspace.main_namespace:
|
||||
self.forbidden_name = [
|
||||
self.objectspace.main_namespace
|
||||
]
|
||||
for extra in self.objectspace.extra_dictionaries:
|
||||
self.forbidden_name.append(extra)
|
||||
else:
|
||||
self.forbidden_name = []
|
||||
# default type inference from a default value with :term:`basic types`
|
||||
self.basic_types = {str: "string", int: "number", bool: "boolean", float: "float"}
|
||||
self.convert_variable()
|
||||
self.convert_test()
|
||||
self.convert_examples()
|
||||
self.convert_help()
|
||||
self.verify_choices()
|
||||
|
||||
def convert_variable(self):
|
||||
"""convert variable"""
|
||||
for variable in self.get_variables():
|
||||
if variable.version != "1.0":
|
||||
if variable.type == "symlink":
|
||||
continue
|
||||
self._convert_variable_inference(variable)
|
||||
for variable in self.get_variables():
|
||||
if variable.type == "symlink":
|
||||
continue
|
||||
if variable.version != "1.0":
|
||||
self._default_variable_copy_informations(variable)
|
||||
if variable.multi is None:
|
||||
variable.multi = False
|
||||
if variable.type is None:
|
||||
variable.type = "string"
|
||||
self.objectspace.informations.add(
|
||||
variable.path, "type", variable.type
|
||||
)
|
||||
self._convert_variable(variable)
|
||||
|
||||
def _convert_variable_inference(
|
||||
self,
|
||||
variable,
|
||||
) -> None:
|
||||
# variable has no type
|
||||
if variable.type is None:
|
||||
# choice type inference from the `choices` attribute
|
||||
if variable.choices is not None:
|
||||
variable.type = "choice"
|
||||
elif variable.regexp is not None:
|
||||
variable.type = "regexp"
|
||||
elif variable.default not in [None, []]:
|
||||
if isinstance(variable.default, list):
|
||||
tested_value = variable.default[0]
|
||||
else:
|
||||
tested_value = variable.default
|
||||
variable.type = self.basic_types.get(type(tested_value), None)
|
||||
# variable has no multi attribute
|
||||
if variable.multi is None and not (variable.type is None and isinstance(variable.default, VariableCalculation)):
|
||||
if variable.path in self.objectspace.leaders:
|
||||
variable.multi = True
|
||||
else:
|
||||
variable.multi = isinstance(variable.default, list)
|
||||
|
||||
def _default_variable_copy_informations(
|
||||
self,
|
||||
variable,
|
||||
) -> None:
|
||||
# if a variable has a variable as default value, that means the type/params or multi should has same value
|
||||
if variable.type is not None or not isinstance(variable.default, VariableCalculation):
|
||||
return
|
||||
# copy type and params
|
||||
calculated_variable_path = variable.default.variable
|
||||
calculated_variable, suffix = self.objectspace.paths.get_with_dynamic(
|
||||
calculated_variable_path, variable.default.path_prefix, variable.path, variable.version, variable.namespace, variable.xmlfiles
|
||||
)
|
||||
if calculated_variable is None:
|
||||
return
|
||||
variable.type = calculated_variable.type
|
||||
if variable.params is None and calculated_variable.params is not None:
|
||||
variable.params = calculated_variable.params
|
||||
# copy multi attribut
|
||||
if variable.multi is None:
|
||||
calculated_path = calculated_variable.path
|
||||
if calculated_path in self.objectspace.leaders and variable.path in self.objectspace.followers and calculated_path.rsplit('.')[0] == variable.path.rsplit('.')[0]:
|
||||
variable.multi = False
|
||||
else:
|
||||
variable.multi = calculated_variable.multi
|
||||
|
||||
def _convert_variable(
|
||||
self,
|
||||
variable: dict,
|
||||
|
|
@ -97,25 +167,42 @@ class Annotator(Walk): # pylint: disable=R0903
|
|||
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.path}" in a leadership must be multi')
|
||||
raise DictConsistencyError(msg, 32, variable.xmlfiles)
|
||||
variable.multi = self.objectspace.multis[variable.path] = True
|
||||
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
|
||||
if variable.choices is not None and variable.type != 'choice':
|
||||
msg = _(f'the variable "{variable.path}" has choices attribut but has not the "choice" type')
|
||||
raise DictConsistencyError(msg, 11, variable.xmlfiles)
|
||||
if variable.regexp is not None and variable.type != 'regexp':
|
||||
msg = _(f'the variable "{variable.path}" has regexp attribut but has not the "regexp" type')
|
||||
raise DictConsistencyError(msg, 37, variable.xmlfiles)
|
||||
|
||||
def convert_test(self):
|
||||
"""Convert variable tests value"""
|
||||
for variable in self.get_variables():
|
||||
if variable.type == "symlink":
|
||||
continue
|
||||
if variable.test is None:
|
||||
# with we want remove test, we set "" has test value
|
||||
continue
|
||||
self.objectspace.informations.add(
|
||||
variable.path, "test", tuple(variable.test)
|
||||
)
|
||||
|
||||
def convert_examples(self):
|
||||
"""Convert variable tests value"""
|
||||
for variable in self.get_variables():
|
||||
if variable.type == "symlink":
|
||||
continue
|
||||
if variable.examples is None:
|
||||
continue
|
||||
self.objectspace.informations.add(
|
||||
variable.path, "examples", tuple(variable.examples)
|
||||
)
|
||||
|
||||
def convert_help(self):
|
||||
"""Convert variable help"""
|
||||
for variable in self.get_variables():
|
||||
|
|
@ -123,3 +210,28 @@ class Annotator(Walk): # pylint: disable=R0903
|
|||
continue
|
||||
self.objectspace.informations.add(variable.path, "help", variable.help)
|
||||
del variable.help
|
||||
|
||||
def verify_choices(self):
|
||||
for variable in self.get_variables():
|
||||
if variable.type != 'choice' or variable.default is None:
|
||||
continue
|
||||
if not isinstance(variable.choices, list):
|
||||
continue
|
||||
choices = variable.choices
|
||||
has_calculation = False
|
||||
for choice in choices:
|
||||
if isinstance(choice, Calculation):
|
||||
has_calculation = True
|
||||
break
|
||||
if has_calculation:
|
||||
continue
|
||||
|
||||
default = variable.default
|
||||
if not isinstance(default, list):
|
||||
default = [default]
|
||||
for value in default:
|
||||
if isinstance(value, Calculation):
|
||||
continue
|
||||
if value not in choices:
|
||||
msg = _(f'the variable "{variable.path}" has an unvalid default value "{value}" should be in {display_list(choices, separator="or", add_quote=True)}')
|
||||
raise DictConsistencyError(msg, 26, variable.xmlfiles)
|
||||
|
|
|
|||
|
|
@ -28,54 +28,427 @@ 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 join, abspath, dirname
|
||||
from pathlib import Path
|
||||
from tiramisu import Config
|
||||
from ruamel.yaml import YAML
|
||||
from .utils import _, load_modules, normalize_family
|
||||
from .convert import RougailConvert
|
||||
|
||||
|
||||
ROUGAILROOT = "/srv/rougail"
|
||||
DTDDIR = join(dirname(abspath(__file__)), "data")
|
||||
RENAMED = {'dictionaries_dir': 'main_dictionaries',
|
||||
'variable_namespace': 'main_namespace',
|
||||
'functions_file': 'functions_files',
|
||||
}
|
||||
NOT_IN_TIRAMISU = {'custom_types': {},
|
||||
}
|
||||
SUBMODULES = None
|
||||
|
||||
|
||||
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"),
|
||||
"tmp_dir": join(ROUGAILROOT, "tmp"),
|
||||
"dtdfilename": join(DTDDIR, "rougail.dtd"),
|
||||
"yamlschema_filename": join(DTDDIR, "rougail.yml"),
|
||||
"functions_file": join(ROUGAILROOT, "functions.py"),
|
||||
"system_service_directory": "/usr/lib/systemd/system",
|
||||
"systemd_service_destination_directory": "/usr/local/lib",
|
||||
"systemd_service_directory": "/systemd",
|
||||
"systemd_service_file": "rougail.conf",
|
||||
"systemd_service_ip_file": "rougail_ip.conf",
|
||||
"systemd_tmpfile_factory_dir": "/usr/local/lib",
|
||||
"systemd_tmpfile_directory": "/tmpfiles.d",
|
||||
"systemd_tmpfile_file": "0rougail.conf",
|
||||
"systemd_tmpfile_delete_before_create": False,
|
||||
"variable_namespace": "rougail",
|
||||
"variable_namespace_description": "Rougail",
|
||||
"auto_freeze_variable": "server_deployed",
|
||||
"internal_functions": [],
|
||||
"multi_functions": [],
|
||||
"extra_annotators": [],
|
||||
"modes_level": ["basic", "standard", "advanced"],
|
||||
"default_family_mode": "basic",
|
||||
"default_variable_mode": "standard",
|
||||
"default_files_engine": "jinja",
|
||||
"default_files_mode": 644,
|
||||
"default_files_owner": "root",
|
||||
"default_files_group": "root",
|
||||
"default_files_included": "no",
|
||||
"default_overrides_engine": "jinja",
|
||||
"default_service_names_engine": "none",
|
||||
"default_certificate_domain": "rougail.server_name",
|
||||
"base_option_name": "baseoption",
|
||||
"export_with_import": True,
|
||||
"force_convert_dyn_option_description": False,
|
||||
"suffix": "",
|
||||
"tiramisu_cache": None,
|
||||
"custom_types": {},
|
||||
}
|
||||
def get_sub_modules():
|
||||
global SUBMODULES
|
||||
if SUBMODULES is None:
|
||||
SUBMODULES = {}
|
||||
for submodule in Path(__file__).parent.iterdir():
|
||||
if submodule.name.startswith('_') or not submodule.is_dir():
|
||||
continue
|
||||
config_file = submodule / 'config.py'
|
||||
if config_file.is_file():
|
||||
SUBMODULES[submodule.name] = load_modules('rougail.' + submodule.name + '.config', str(config_file))
|
||||
return SUBMODULES
|
||||
|
||||
|
||||
def get_level(module):
|
||||
return module['level']
|
||||
|
||||
|
||||
class _RougailConfig:
|
||||
def __init__(self,
|
||||
backward_compatibility: bool,
|
||||
root,
|
||||
extra_vars: dict
|
||||
):
|
||||
self.backward_compatibility = backward_compatibility
|
||||
self.root = root
|
||||
self.config = Config(
|
||||
self.root,
|
||||
)
|
||||
self.config.property.read_only()
|
||||
self.extra_vars = extra_vars
|
||||
self.not_in_tiramisu = NOT_IN_TIRAMISU | extra_vars
|
||||
for variable, default_value in self.not_in_tiramisu.items():
|
||||
if not isinstance(default_value, str):
|
||||
default_value = default_value.copy()
|
||||
setattr(self, variable, default_value)
|
||||
|
||||
def copy(self):
|
||||
rougailconfig = _RougailConfig(self.backward_compatibility, self.root, self.extra_vars)
|
||||
rougailconfig.config.value.importation(self.config.value.exportation())
|
||||
rougailconfig.config.property.importation(self.config.property.exportation())
|
||||
rougailconfig.config.property.read_only()
|
||||
for variable in self.not_in_tiramisu:
|
||||
value = getattr(self, variable)
|
||||
if not isinstance(value, str):
|
||||
value = value.copy()
|
||||
setattr(rougailconfig, variable, value)
|
||||
return rougailconfig
|
||||
|
||||
def __setitem__(self,
|
||||
key,
|
||||
value,
|
||||
) -> None:
|
||||
if key in self.not_in_tiramisu:
|
||||
setattr(self, key, value)
|
||||
else:
|
||||
self.config.property.read_write()
|
||||
if key == 'export_with_import':
|
||||
key = 'not_export_with_import'
|
||||
key = RENAMED.get(key, key)
|
||||
option = self.config.option(key)
|
||||
if option.isoptiondescription() and option.isleadership():
|
||||
leader = list(value)
|
||||
option.leader().value.reset()
|
||||
option.leader().value.set(leader)
|
||||
follower = option.followers()[0]
|
||||
for idx, val in enumerate(value.values()):
|
||||
self.config.option(follower.path(), idx).value.set(val)
|
||||
elif key == 'not_export_with_import':
|
||||
option.value.set(not value)
|
||||
else:
|
||||
option.value.set(value)
|
||||
self.config.property.read_only()
|
||||
|
||||
def __getitem__(self,
|
||||
key,
|
||||
) -> None:
|
||||
if key in self.not_in_tiramisu:
|
||||
return getattr(self, key)
|
||||
if key == 'export_with_import':
|
||||
key = 'not_export_with_import'
|
||||
option = self.config.option(key)
|
||||
if option.isoptiondescription() and option.isleadership():
|
||||
return self.get_leadership(option)
|
||||
ret = self.config.option(key).value.get()
|
||||
if key == 'not_export_with_import':
|
||||
return not ret
|
||||
return ret
|
||||
|
||||
def get_leadership(self,
|
||||
option
|
||||
) -> dict:
|
||||
leader = None
|
||||
followers = []
|
||||
for opt, value in option.value.get().items():
|
||||
if opt.issymlinkoption():
|
||||
continue
|
||||
if leader is None:
|
||||
leader = value
|
||||
else:
|
||||
followers.append(value)
|
||||
return dict(zip(leader, followers))
|
||||
|
||||
def parse(self, config) -> str:
|
||||
for option in config:
|
||||
if option.isoptiondescription():
|
||||
yield from self.parse(option)
|
||||
elif not option.issymlinkoption():
|
||||
yield f'{option.path()}: {option.value.get()}'
|
||||
|
||||
def __repr__(self):
|
||||
self.config.property.read_write()
|
||||
try:
|
||||
values = "\n".join(self.parse(self.config))
|
||||
except Exception as err:
|
||||
values = str(err)
|
||||
self.config.property.read_only()
|
||||
return values
|
||||
|
||||
|
||||
class FakeRougailConvert(RougailConvert):
|
||||
def __init__(self,
|
||||
add_extra_options: bool,
|
||||
) -> None:
|
||||
self.add_extra_options = add_extra_options
|
||||
super().__init__({})
|
||||
|
||||
def load_config(self) -> None:
|
||||
self.sort_dictionaries_all = False
|
||||
self.main_namespace = None
|
||||
self.suffix = ''
|
||||
self.custom_types = {}
|
||||
self.functions_files = []
|
||||
self.modes_level = []
|
||||
self.extra_annotators = []
|
||||
self.base_option_name = "baseoption"
|
||||
self.export_with_import = True
|
||||
self.internal_functions = []
|
||||
self.plugins = ['structural_commandline']
|
||||
self.add_extra_options = self.add_extra_options
|
||||
|
||||
|
||||
def get_rougail_config(*,
|
||||
backward_compatibility: bool=True,
|
||||
add_extra_options: bool=True,
|
||||
) -> _RougailConfig:
|
||||
if backward_compatibility:
|
||||
main_namespace_default = 'rougail'
|
||||
else:
|
||||
main_namespace_default = 'null'
|
||||
rougail_options = """default_dictionary_format_version:
|
||||
description: Dictionary format version by default, if not specified in dictionary file
|
||||
alternative_name: v
|
||||
choices:
|
||||
- '1.0'
|
||||
- '1.1'
|
||||
mandatory: false
|
||||
|
||||
main_dictionaries:
|
||||
description: 'Directories where dictionary files are placed'
|
||||
type: unix_filename
|
||||
alternative_name: m
|
||||
params:
|
||||
allow_relative: True
|
||||
test_existence: True
|
||||
types:
|
||||
- directory
|
||||
multi: true
|
||||
|
||||
sort_dictionaries_all:
|
||||
description: Sort dictionaries from differents directories
|
||||
negative_description: Sort dictionaries directory by directory
|
||||
default: false
|
||||
|
||||
main_namespace:
|
||||
description: Main namespace name
|
||||
default: MAIN_MAMESPACE_DEFAULT
|
||||
alternative_name: s
|
||||
mandatory: false
|
||||
|
||||
extra_dictionaries:
|
||||
description: Extra namespaces
|
||||
type: leadership
|
||||
disabled:
|
||||
variable: main_namespace
|
||||
when: null
|
||||
|
||||
names:
|
||||
description: 'Extra namespace name'
|
||||
alternative_name: xn
|
||||
multi: true
|
||||
mandatory: false
|
||||
|
||||
directories:
|
||||
description: Directories where extra dictionary files are placed
|
||||
alternative_name: xd
|
||||
type: unix_filename
|
||||
params:
|
||||
allow_relative: true
|
||||
test_existence: true
|
||||
types:
|
||||
- directory
|
||||
multi: true
|
||||
|
||||
upgrade:
|
||||
description: Update dictionaries to newest Rougail format version
|
||||
negative_description: Do not update dictionaries to newest Rougail format version
|
||||
default: false
|
||||
|
||||
upgrade_options:
|
||||
description: Update informations
|
||||
disabled:
|
||||
variable: upgrade
|
||||
when: false
|
||||
|
||||
main_dictionaries:
|
||||
description: 'Directories where dictionary files will be placed'
|
||||
default:
|
||||
variable: __.main_dictionaries
|
||||
|
||||
extra_dictionary:
|
||||
description: 'Directories where extra files will be placed'
|
||||
type: unix_filename
|
||||
params:
|
||||
allow_relative: true
|
||||
test_existence: true
|
||||
types:
|
||||
- directory
|
||||
disabled:
|
||||
variable: __.main_namespace
|
||||
when: null
|
||||
|
||||
functions_files:
|
||||
description: File with functions
|
||||
alternative_name: c
|
||||
type: unix_filename
|
||||
params:
|
||||
allow_relative: true
|
||||
test_existence: true
|
||||
types:
|
||||
- file
|
||||
multi: true
|
||||
mandatory: false
|
||||
|
||||
modes_level:
|
||||
description: All modes level available
|
||||
default:
|
||||
- basic
|
||||
- standard
|
||||
- advanced
|
||||
commandline: false
|
||||
|
||||
default_family_mode:
|
||||
description: Default mode for a family
|
||||
default:
|
||||
type: jinja
|
||||
jinja: |
|
||||
{{ modes_level[0] }}
|
||||
validators:
|
||||
- type: jinja
|
||||
jinja: |
|
||||
{% if default_family_mode not in modes_level %}
|
||||
not in modes_level ({modes_level})
|
||||
{% endif %}
|
||||
commandline: false
|
||||
|
||||
default_variable_mode:
|
||||
description: Default mode for a variable
|
||||
default:
|
||||
type: jinja
|
||||
jinja: |
|
||||
{{ modes_level[1] }}
|
||||
validators:
|
||||
- type: jinja
|
||||
jinja: |
|
||||
{% if default_variable_mode not in modes_level %}
|
||||
not in modes_level ({modes_level})
|
||||
{% endif %}
|
||||
commandline: false
|
||||
|
||||
base_option_name:
|
||||
description: Option name for the base option
|
||||
default: baseoption
|
||||
commandline: false
|
||||
|
||||
not_export_with_import:
|
||||
description: In cache file, do not importation of Tiramisu and other dependencies
|
||||
default: false
|
||||
commandline: false
|
||||
|
||||
tiramisu_cache:
|
||||
description: Tiramisu cache filename
|
||||
alternative_name: t
|
||||
type: unix_filename
|
||||
mandatory: false
|
||||
params:
|
||||
allow_relative: true
|
||||
|
||||
internal_functions:
|
||||
description: Name of internal functions that we can use as a function
|
||||
multi: true
|
||||
mandatory: false
|
||||
commandline: false
|
||||
|
||||
extra_annotators:
|
||||
description: Name of extra annotators
|
||||
multi: true
|
||||
mandatory: false
|
||||
commandline: false
|
||||
|
||||
plugins:
|
||||
description: Name of Rougail plugins
|
||||
multi: true
|
||||
mandatory: false
|
||||
commandline: false
|
||||
|
||||
suffix:
|
||||
description: Suffix add to generated option name
|
||||
default: ''
|
||||
mandatory: false
|
||||
commandline: false
|
||||
""".replace('MAIN_MAMESPACE_DEFAULT', main_namespace_default)
|
||||
processes = {'structural': [],
|
||||
'output': [],
|
||||
'user data': [],
|
||||
}
|
||||
for module in get_sub_modules().values():
|
||||
data = module.get_rougail_config()
|
||||
processes[data['process']].append(data)
|
||||
# reorder
|
||||
for process in processes:
|
||||
processes[process] = list(sorted(processes[process], key=get_level))
|
||||
rougail_process = """step: # Load and exporter steps
|
||||
disabled:
|
||||
variable: upgrade"""
|
||||
for process in processes:
|
||||
if processes[process]:
|
||||
objects = processes[process]
|
||||
rougail_process += """
|
||||
{NAME}:
|
||||
description: Select for {NAME}
|
||||
alternative_name: {NAME[0]}
|
||||
choices:
|
||||
""".format(NAME=normalize_family(process),
|
||||
)
|
||||
for obj in objects:
|
||||
rougail_process += f" - {obj['name']}\n"
|
||||
if process == 'structural':
|
||||
rougail_process += " commandline: false"
|
||||
elif process == 'user data':
|
||||
rougail_process += """ multi: true
|
||||
mandatory: false
|
||||
"""
|
||||
hidden_outputs = [process['name'] for process in processes['output'] if not process.get('allow_user_data', True)]
|
||||
if hidden_outputs:
|
||||
rougail_process += """ hidden:
|
||||
type: jinja
|
||||
jinja: |
|
||||
"""
|
||||
for hidden_output in hidden_outputs:
|
||||
rougail_process += """ {% if _.output == 'NAME' %}
|
||||
Cannot load user data for NAME output
|
||||
{% endif %}
|
||||
""".replace('NAME', hidden_output)
|
||||
else:
|
||||
rougail_process += ' default: {DEFAULT}'.format(DEFAULT=objects[0]['name'])
|
||||
else:
|
||||
rougail_process += """
|
||||
{NAME}:
|
||||
description: Select for {NAME}
|
||||
hidden: true
|
||||
mandatory: false
|
||||
multi: true
|
||||
""".format(NAME=normalize_family(process),
|
||||
)
|
||||
rougail_options += rougail_process
|
||||
convert = FakeRougailConvert(add_extra_options)
|
||||
convert._init()
|
||||
convert.namespace = None
|
||||
convert.parse_root_file(
|
||||
'rougail.config',
|
||||
'',
|
||||
'1.1',
|
||||
YAML().load(rougail_options),
|
||||
)
|
||||
extra_vars = {}
|
||||
for process in processes:
|
||||
for obj in processes[process]:
|
||||
if 'extra_vars' in obj:
|
||||
extra_vars |= obj['extra_vars']
|
||||
if not 'options' in obj:
|
||||
continue
|
||||
convert.parse_root_file(
|
||||
f'rougail.config.{obj["name"]}',
|
||||
'',
|
||||
'1.1',
|
||||
YAML().load(obj['options']),
|
||||
)
|
||||
|
||||
tiram_obj = convert.save(None)
|
||||
optiondescription = {}
|
||||
exec(tiram_obj, {}, optiondescription) # pylint: disable=W0122
|
||||
return _RougailConfig(backward_compatibility,
|
||||
optiondescription["option_0"],
|
||||
extra_vars=extra_vars,
|
||||
)
|
||||
|
||||
|
||||
RougailConfig = get_rougail_config()
|
||||
|
|
|
|||
|
|
@ -73,3 +73,14 @@ class DictConsistencyError(Exception):
|
|||
|
||||
class UpgradeError(Exception):
|
||||
"""Error during XML upgrade"""
|
||||
|
||||
## ---- generic exceptions ----
|
||||
|
||||
class NotFoundError(Exception):
|
||||
"not found error"
|
||||
pass
|
||||
|
||||
## ---- specific exceptions ----
|
||||
|
||||
class VariableCalculationDependencyError(Exception):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -29,10 +29,12 @@ from pydantic import (
|
|||
StrictStr,
|
||||
ConfigDict,
|
||||
)
|
||||
from tiramisu import undefined
|
||||
from .utils import get_jinja_variable_to_param, get_realpath
|
||||
|
||||
from .error import DictConsistencyError, VariableCalculationDependencyError
|
||||
|
||||
BASETYPE = Union[StrictBool, StrictInt, StrictFloat, StrictStr, None]
|
||||
PROPERTY_ATTRIBUTE = ["frozen", "hidden", "disabled", "mandatory"]
|
||||
|
||||
|
||||
def convert_boolean(value: str) -> bool:
|
||||
|
|
@ -44,44 +46,63 @@ def convert_boolean(value: str) -> bool:
|
|||
return True
|
||||
elif value == "false":
|
||||
return False
|
||||
raise Exception(f"unknown boolean value {value}")
|
||||
elif value in ["", None]:
|
||||
return None
|
||||
raise Exception(f'unknown boolean value "{value}"')
|
||||
|
||||
|
||||
CONVERT_OPTION = {
|
||||
"string": dict(opttype="StrOption"),
|
||||
"number": dict(opttype="IntOption", func=int),
|
||||
"float": dict(opttype="FloatOption", func=float),
|
||||
"string": dict(opttype="StrOption", example="example"),
|
||||
"number": dict(opttype="IntOption", func=int, example=42),
|
||||
"float": dict(opttype="FloatOption", func=float, example=1.42),
|
||||
"boolean": dict(opttype="BoolOption", func=convert_boolean),
|
||||
"secret": dict(opttype="PasswordOption"),
|
||||
"mail": dict(opttype="EmailOption"),
|
||||
"unix_filename": dict(opttype="FilenameOption"),
|
||||
"date": dict(opttype="DateOption"),
|
||||
"unix_user": dict(opttype="UsernameOption"),
|
||||
"ip": dict(opttype="IPOption", initkwargs={"allow_reserved": 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"),
|
||||
"secret": dict(opttype="PasswordOption", example="secrets"),
|
||||
"mail": dict(opttype="EmailOption", example="user@example.net"),
|
||||
"unix_filename": dict(opttype="FilenameOption", example="/tmp/myfile.txt"),
|
||||
"date": dict(opttype="DateOption", example="2000-01-01"),
|
||||
"unix_user": dict(opttype="UsernameOption", example="username"),
|
||||
"ip": dict(
|
||||
opttype="IPOption", initkwargs={"allow_reserved": True}, example="1.1.1.1"
|
||||
),
|
||||
"cidr": dict(opttype="IPOption", initkwargs={"cidr": True}, example="1.1.1.0/24"),
|
||||
"netmask": dict(opttype="NetmaskOption", example="255.255.255.0"),
|
||||
"network": dict(opttype="NetworkOption", example="1.1.1.0"),
|
||||
"network_cidr": dict(
|
||||
opttype="NetworkOption", initkwargs={"cidr": True}, example="1.1.1.0/24"
|
||||
),
|
||||
"broadcast": dict(opttype="BroadcastOption", example="1.1.1.255"),
|
||||
"netbios": dict(
|
||||
opttype="DomainnameOption",
|
||||
initkwargs={"type": "netbios", "warnings_only": True},
|
||||
example="example",
|
||||
),
|
||||
"domainname": dict(
|
||||
opttype="DomainnameOption", initkwargs={"type": "domainname", "allow_ip": False}
|
||||
opttype="DomainnameOption",
|
||||
initkwargs={"type": "domainname", "allow_ip": False},
|
||||
example="example.net",
|
||||
),
|
||||
"hostname": dict(
|
||||
opttype="DomainnameOption", initkwargs={"type": "hostname", "allow_ip": False}
|
||||
opttype="DomainnameOption",
|
||||
initkwargs={"type": "hostname", "allow_ip": False},
|
||||
example="example",
|
||||
),
|
||||
"web_address": dict(
|
||||
opttype="URLOption", initkwargs={"allow_ip": False, "allow_without_dot": True}
|
||||
opttype="URLOption",
|
||||
initkwargs={"allow_ip": False, "allow_without_dot": True},
|
||||
example="https://example.net",
|
||||
),
|
||||
"port": dict(opttype="PortOption", initkwargs={"allow_private": True}),
|
||||
"mac": dict(opttype="MACOption"),
|
||||
"port": dict(
|
||||
opttype="PortOption", initkwargs={"allow_private": True}, example="111"
|
||||
),
|
||||
"mac": dict(opttype="MACOption", example="00:00:00:00:00"),
|
||||
"unix_permissions": dict(
|
||||
opttype="PermissionsOption", initkwargs={"warnings_only": True}, func=int
|
||||
opttype="PermissionsOption",
|
||||
initkwargs={"warnings_only": True},
|
||||
func=int,
|
||||
example="644",
|
||||
),
|
||||
"choice": dict(opttype="ChoiceOption"),
|
||||
"choice": dict(opttype="ChoiceOption", example="a_choice"),
|
||||
"regexp": dict(opttype="RegexpOption"),
|
||||
#
|
||||
"symlink": dict(opttype="SymLinkOption"),
|
||||
}
|
||||
|
|
@ -89,24 +110,45 @@ CONVERT_OPTION = {
|
|||
|
||||
class Param(BaseModel):
|
||||
key: str
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path,
|
||||
attribute,
|
||||
family_is_dynamic,
|
||||
is_follower,
|
||||
xmlfiles,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class AnyParam(Param):
|
||||
type: str
|
||||
value: BASETYPE
|
||||
value: Union[BASETYPE, List[BASETYPE]]
|
||||
|
||||
|
||||
class VariableParam(Param):
|
||||
type: str
|
||||
variable: str
|
||||
propertyerror: bool = True
|
||||
whole: bool = False
|
||||
optional: bool = False
|
||||
|
||||
|
||||
class SuffixParam(Param):
|
||||
type: str
|
||||
suffix: Optional[int] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
if not kwargs["family_is_dynamic"]:
|
||||
msg = f'suffix parameter for "{kwargs["attribute"]}" in "{kwargs["path"]}" cannot be set none dynamic family'
|
||||
raise DictConsistencyError(msg, 10, kwargs["xmlfiles"])
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class InformationParam(Param):
|
||||
|
|
@ -118,6 +160,16 @@ class InformationParam(Param):
|
|||
class IndexParam(Param):
|
||||
type: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
|
||||
if not kwargs["is_follower"]:
|
||||
msg = f'the variable "{kwargs["path"]}" is not a follower, so cannot have index type for param in "{kwargs["attribute"]}"'
|
||||
raise DictConsistencyError(msg, 25, kwargs["xmlfiles"])
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
PARAM_TYPES = {
|
||||
"any": AnyParam,
|
||||
|
|
@ -132,6 +184,11 @@ class Calculation(BaseModel):
|
|||
path_prefix: Optional[str]
|
||||
path: str
|
||||
inside_list: bool
|
||||
version: str
|
||||
ori_path: Optional[str] = None
|
||||
default_values: Any = None
|
||||
namespace: Optional[str]
|
||||
xmlfiles: List[str]
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
|
|
@ -148,24 +205,49 @@ class Calculation(BaseModel):
|
|||
for param_obj in self.params:
|
||||
param = param_obj.model_dump()
|
||||
if param.get("type") == "variable":
|
||||
variable_path = self.get_realpath(param["variable"])
|
||||
variable, suffix, dynamic = objectspace.paths.get_with_dynamic(variable_path)
|
||||
if self.ori_path is None:
|
||||
path = self.path
|
||||
else:
|
||||
path = self.ori_path
|
||||
variable, suffix = objectspace.paths.get_with_dynamic(
|
||||
param["variable"],
|
||||
self.path_prefix,
|
||||
path,
|
||||
self.version,
|
||||
self.namespace,
|
||||
self.xmlfiles,
|
||||
)
|
||||
if not variable:
|
||||
if not param.get("optional"):
|
||||
raise Exception(f"cannot find {variable_path}")
|
||||
msg = f'cannot find variable "{param["variable"]}" defined attribute in "{self.attribute_name}" for "{self.path}"'
|
||||
raise DictConsistencyError(msg, 22, self.xmlfiles)
|
||||
continue
|
||||
if not isinstance(variable, objectspace.variable):
|
||||
raise Exception("pfff it's a family")
|
||||
param["variable"] = variable
|
||||
if suffix:
|
||||
param["suffix"] = suffix
|
||||
param["dynamic"] = dynamic
|
||||
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")
|
||||
if self.ori_path is None:
|
||||
path = self.path
|
||||
else:
|
||||
path = self.ori_path
|
||||
variable, suffix = objectspace.paths.get_with_dynamic(
|
||||
param["variable"],
|
||||
self.path_prefix,
|
||||
path,
|
||||
self.version,
|
||||
self.namespace,
|
||||
self.xmlfiles,
|
||||
)
|
||||
if not variable:
|
||||
msg = f'cannot find variable "{param["variable"]}" defined in "{self.attribute_name}" for "{self.path}"'
|
||||
raise DictConsistencyError(msg, 14, self.xmlfiles)
|
||||
param["variable"] = variable
|
||||
if suffix:
|
||||
msg = f'variable "{param["variable"]}" defined in "{self.attribute_name}" for "{self.path}" is a dynamic variable'
|
||||
raise DictConsistencyError(msg, 15, self.xmlfiles)
|
||||
else:
|
||||
del param["variable"]
|
||||
params[param.pop("key")] = param
|
||||
|
|
@ -174,11 +256,20 @@ class Calculation(BaseModel):
|
|||
|
||||
class JinjaCalculation(Calculation):
|
||||
attribute_name: Literal[
|
||||
"frozen", "hidden", "mandatory", "disabled", "default", "validators", "choices"
|
||||
"frozen",
|
||||
"hidden",
|
||||
"mandatory",
|
||||
"empty",
|
||||
"disabled",
|
||||
"default",
|
||||
"validators",
|
||||
"choices",
|
||||
"dynamic",
|
||||
]
|
||||
jinja: StrictStr
|
||||
params: Optional[List[Param]] = None
|
||||
return_type: BASETYPE = None
|
||||
description: Optional[StrictStr] = None
|
||||
|
||||
def _jinja_to_function(
|
||||
self,
|
||||
|
|
@ -203,29 +294,47 @@ class JinjaCalculation(Calculation):
|
|||
"__internal_jinja": jinja_path,
|
||||
"__internal_type": return_type,
|
||||
"__internal_multi": multi,
|
||||
"__internal_files": self.xmlfiles,
|
||||
"__internal_attribute": self.attribute_name,
|
||||
"__internal_variable": self.path,
|
||||
},
|
||||
}
|
||||
if self.default_values:
|
||||
default["params"]["__default_value"] = self.default_values
|
||||
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, dynamic in get_jinja_variable_to_param(
|
||||
if self.ori_path is None:
|
||||
path = self.path
|
||||
else:
|
||||
path = self.ori_path
|
||||
for sub_variable, suffix, true_path in get_jinja_variable_to_param(
|
||||
path,
|
||||
self.jinja,
|
||||
objectspace,
|
||||
variable.xmlfiles,
|
||||
objectspace.functions,
|
||||
self.path_prefix,
|
||||
self.version,
|
||||
self.namespace,
|
||||
):
|
||||
if sub_variable.path in objectspace.variables:
|
||||
if true_path in default["params"]:
|
||||
continue
|
||||
if isinstance(sub_variable, dict):
|
||||
default["params"][true_path] = {
|
||||
"type": "value",
|
||||
"value": sub_variable,
|
||||
}
|
||||
else:
|
||||
default["params"][true_path] = {
|
||||
"type": "variable",
|
||||
"variable": sub_variable,
|
||||
}
|
||||
if suffix:
|
||||
default["params"][true_path]["suffix"] = suffix
|
||||
default["params"][true_path]["dynamic"] = dynamic
|
||||
return default
|
||||
|
||||
def to_function(
|
||||
|
|
@ -258,7 +367,7 @@ class JinjaCalculation(Calculation):
|
|||
False,
|
||||
objectspace,
|
||||
)
|
||||
elif self.attribute_name in ["frozen", "hidden", "disabled", "mandatory"]:
|
||||
elif self.attribute_name in PROPERTY_ATTRIBUTE:
|
||||
if self.return_type:
|
||||
raise Exception("return_type not allowed!")
|
||||
return self._jinja_to_function(
|
||||
|
|
@ -267,7 +376,7 @@ class JinjaCalculation(Calculation):
|
|||
False,
|
||||
objectspace,
|
||||
add_help=True,
|
||||
params={None: [self.attribute_name]},
|
||||
params={None: [self.attribute_name], "when": True, "inverse": False},
|
||||
)
|
||||
elif self.attribute_name == "choices":
|
||||
return_type = self.return_type
|
||||
|
|
@ -279,26 +388,52 @@ class JinjaCalculation(Calculation):
|
|||
not self.inside_list,
|
||||
objectspace,
|
||||
)
|
||||
elif self.attribute_name == "dynamic":
|
||||
return self._jinja_to_function(
|
||||
"jinja_to_function",
|
||||
"string",
|
||||
True,
|
||||
objectspace,
|
||||
)
|
||||
raise Exception("hu?")
|
||||
|
||||
|
||||
class VariableCalculation(Calculation):
|
||||
attribute_name: Literal[
|
||||
"frozen", "hidden", "mandatory", "disabled", "default", "choices"
|
||||
]
|
||||
class _VariableCalculation(Calculation):
|
||||
variable: StrictStr
|
||||
propertyerror: bool = True
|
||||
allow_none: bool = False
|
||||
|
||||
def to_function(
|
||||
def get_variable(self,
|
||||
objectspace,
|
||||
) -> "Variable":
|
||||
if self.ori_path is None:
|
||||
path = self.path
|
||||
else:
|
||||
path = self.ori_path
|
||||
variable, suffix = objectspace.paths.get_with_dynamic(
|
||||
self.variable,
|
||||
self.path_prefix,
|
||||
path,
|
||||
self.version,
|
||||
self.namespace,
|
||||
self.xmlfiles,
|
||||
)
|
||||
if variable and not isinstance(variable, objectspace.variable):
|
||||
# FIXME remove the pfff
|
||||
raise Exception("pfff it's a family")
|
||||
return variable, suffix
|
||||
|
||||
def get_params(
|
||||
self,
|
||||
objectspace,
|
||||
) -> dict:
|
||||
variable_path = self.get_realpath(self.variable)
|
||||
variable, suffix, dynamic = objectspace.paths.get_with_dynamic(variable_path)
|
||||
variable: "Variable",
|
||||
suffix: Optional[str],
|
||||
*,
|
||||
needs_multi: Optional[bool] = None,
|
||||
):
|
||||
if not variable:
|
||||
raise Exception(f"pffff {variable_path}")
|
||||
if not isinstance(variable, objectspace.variable):
|
||||
raise Exception("pfff it's a family")
|
||||
msg = f'Variable not found "{self.variable}" for attribut "{self.attribute_name}" for variable "{self.path}"'
|
||||
raise DictConsistencyError(msg, 88, self.xmlfiles)
|
||||
param = {
|
||||
"type": "variable",
|
||||
"variable": variable,
|
||||
|
|
@ -306,36 +441,124 @@ class VariableCalculation(Calculation):
|
|||
}
|
||||
if suffix:
|
||||
param["suffix"] = suffix
|
||||
param["dynamic"] = dynamic
|
||||
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)
|
||||
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
|
||||
if self.default_values:
|
||||
params["__default_value"] = self.default_values
|
||||
if self.allow_none:
|
||||
params["allow_none"] = True
|
||||
if self.inside_list and variable.path in objectspace.multis:
|
||||
raise Exception("pfff")
|
||||
ret = {
|
||||
"function": function,
|
||||
if needs_multi is None:
|
||||
if self.attribute_name != "default":
|
||||
needs_multi = True
|
||||
else:
|
||||
needs_multi = self.path in objectspace.multis
|
||||
calc_variable_is_multi = variable.path in objectspace.multis
|
||||
if not calc_variable_is_multi:
|
||||
if variable.path in objectspace.paths._dynamics and (
|
||||
suffix is None or suffix[-1] is None
|
||||
):
|
||||
self_dyn_path = objectspace.paths._dynamics.get(self.path)
|
||||
if self_dyn_path is not None:
|
||||
var_dyn_path = objectspace.paths._dynamics[variable.path]
|
||||
if self_dyn_path != var_dyn_path and not self_dyn_path.startswith(
|
||||
f"{var_dyn_path}."
|
||||
):
|
||||
calc_variable_is_multi = True
|
||||
else:
|
||||
calc_variable_is_multi = True
|
||||
elif suffix and '{{ suffix }}' in suffix:
|
||||
calc_variable_is_multi = True
|
||||
if needs_multi:
|
||||
if calc_variable_is_multi:
|
||||
if self.inside_list:
|
||||
msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", the variable "{variable.path}" is multi but is inside a list'
|
||||
raise DictConsistencyError(msg, 18, self.xmlfiles)
|
||||
elif not self.inside_list:
|
||||
msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", the variable "{variable.path}" is not multi but is not inside a list'
|
||||
raise DictConsistencyError(msg, 20, self.xmlfiles)
|
||||
elif self.inside_list:
|
||||
msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", it\'s a list'
|
||||
raise DictConsistencyError(msg, 23, self.xmlfiles)
|
||||
elif calc_variable_is_multi:
|
||||
if variable.multi or variable.path.rsplit('.', 1)[0] != self.path.rsplit('.', 1)[0]:
|
||||
# it's not a follower or not in same leadership
|
||||
msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", the variable "{variable.path}" is a multi'
|
||||
raise DictConsistencyError(msg, 21, self.xmlfiles)
|
||||
else:
|
||||
params[None][0]['index'] = {'index': {'type': 'index'}}
|
||||
return params
|
||||
|
||||
|
||||
class VariableCalculation(_VariableCalculation):
|
||||
attribute_name: Literal["default", "choices", "dynamic"]
|
||||
optional: bool = False
|
||||
|
||||
def to_function(
|
||||
self,
|
||||
objectspace,
|
||||
) -> dict:
|
||||
if self.attribute_name != "default" and self.optional:
|
||||
msg = f'"{self.attribute_name}" variable shall not have an "optional" attribute for variable "{self.variable}"'
|
||||
raise DictConsistencyError(msg, 33, self.xmlfiles)
|
||||
variable, suffix = self.get_variable(objectspace)
|
||||
if not variable and self.optional:
|
||||
raise VariableCalculationDependencyError()
|
||||
params = self.get_params(objectspace,
|
||||
variable,
|
||||
suffix,
|
||||
)
|
||||
return {
|
||||
"function": "calc_value",
|
||||
"params": params,
|
||||
}
|
||||
if help_function:
|
||||
ret["help"] = help_function
|
||||
return ret
|
||||
|
||||
|
||||
class VariablePropertyCalculation(_VariableCalculation):
|
||||
attribute_name: Literal[*PROPERTY_ATTRIBUTE]
|
||||
when: Any = undefined
|
||||
when_not: Any = undefined
|
||||
|
||||
def to_function(
|
||||
self,
|
||||
objectspace,
|
||||
) -> dict:
|
||||
variable, suffix = self.get_variable(objectspace)
|
||||
params = self.get_params(objectspace,
|
||||
variable,
|
||||
suffix,
|
||||
needs_multi=False,)
|
||||
variable = params[None][0]["variable"]
|
||||
if self.when is not undefined:
|
||||
if self.version == "1.0":
|
||||
msg = f'when is not allowed in format version 1.0 for attribute "{self.attribute_name}" for variable "{self.path}"'
|
||||
raise DictConsistencyError(msg, 103, variable.xmlfiles)
|
||||
if self.when_not is not undefined:
|
||||
msg = f'the variable "{self.path}" has an invalid attribute "{self.attribute_name}", when and when_not cannot set together'
|
||||
raise DictConsistencyError(msg, 31, variable.xmlfiles)
|
||||
when = self.when
|
||||
inverse = False
|
||||
elif self.when_not is not undefined:
|
||||
if self.version == "1.0":
|
||||
msg = f'when_not is not allowed in format version 1.0 for attribute "{self.attribute_name}" for variable "{self.path}"'
|
||||
raise DictConsistencyError(msg, 104, variable.xmlfiles)
|
||||
when = self.when_not
|
||||
inverse = True
|
||||
else:
|
||||
if variable.type != "boolean":
|
||||
raise Exception("only boolean!")
|
||||
when = True
|
||||
inverse = False
|
||||
params[None].insert(0, self.attribute_name)
|
||||
params["when"] = when
|
||||
params["inverse"] = inverse
|
||||
return {
|
||||
"function": "variable_to_property",
|
||||
"params": params,
|
||||
"help": "variable_to_property",
|
||||
}
|
||||
|
||||
|
||||
class InformationCalculation(Calculation):
|
||||
attribute_name: Literal["default"]
|
||||
attribute_name: Literal["default", "choice", "dynamic"]
|
||||
information: StrictStr
|
||||
variable: Optional[StrictStr]
|
||||
|
||||
|
|
@ -343,42 +566,109 @@ class InformationCalculation(Calculation):
|
|||
self,
|
||||
objectspace,
|
||||
) -> dict:
|
||||
param = {
|
||||
"type": "information",
|
||||
"information": self.information,
|
||||
params = {
|
||||
None: [
|
||||
{
|
||||
"type": "information",
|
||||
"information": self.information,
|
||||
}
|
||||
]
|
||||
}
|
||||
if self.variable:
|
||||
variable_path = self.get_realpath(self.variable)
|
||||
variable = objectspace.paths[variable_path]
|
||||
if variable is None:
|
||||
if self.ori_path is None:
|
||||
path = self.path
|
||||
else:
|
||||
path = self.ori_path
|
||||
variable, suffix = objectspace.paths.get_with_dynamic(
|
||||
self.variable,
|
||||
self.path_prefix,
|
||||
path,
|
||||
self.version,
|
||||
self.namespace,
|
||||
self.xmlfiles,
|
||||
)
|
||||
if variable is None or suffix is not None:
|
||||
raise Exception("pfff")
|
||||
param["variable"] = variable
|
||||
params[None][0]["variable"] = variable
|
||||
if self.default_values:
|
||||
params["__default_value"] = self.default_values
|
||||
return {
|
||||
"function": "calc_value",
|
||||
"params": {None: [param]},
|
||||
"params": params,
|
||||
}
|
||||
|
||||
|
||||
class SuffixCalculation(Calculation):
|
||||
attribute_name: Literal["default"]
|
||||
class _SuffixCalculation(Calculation):
|
||||
suffix: Optional[int] = None
|
||||
|
||||
def get_suffix(self) -> dict:
|
||||
suffix = {"type": "suffix"}
|
||||
if self.suffix is not None:
|
||||
suffix["suffix"] = self.suffix
|
||||
return suffix
|
||||
|
||||
|
||||
class SuffixCalculation(_SuffixCalculation):
|
||||
attribute_name: Literal["default", "choice", "dynamic"]
|
||||
|
||||
def to_function(
|
||||
self,
|
||||
objectspace,
|
||||
) -> dict:
|
||||
suffix = {"type": "suffix"}
|
||||
if self.suffix is not None:
|
||||
suffix["suffix"] = self.suffix
|
||||
return {
|
||||
"function": "calc_value",
|
||||
"params": {None: [{"type": "suffix"}]},
|
||||
"params": {None: [self.get_suffix()]},
|
||||
}
|
||||
|
||||
|
||||
class SuffixPropertyCalculation(_SuffixCalculation):
|
||||
attribute_name: Literal[*PROPERTY_ATTRIBUTE]
|
||||
when: Any = undefined
|
||||
when_not: Any = undefined
|
||||
|
||||
def to_function(
|
||||
self,
|
||||
objectspace,
|
||||
) -> dict:
|
||||
if self.version == "1.0":
|
||||
msg = f'when is not allowed in format version 1.0 for attribute "{self.attribute_name}"'
|
||||
raise DictConsistencyError(msg, 105, variable.xmlfiles)
|
||||
if self.when is not undefined:
|
||||
if self.when_not is not undefined:
|
||||
msg = f'the suffix has an invalid attribute "{self.attribute_name}", when and when_not cannot set together'
|
||||
raise DictConsistencyError(msg, 35, variable.xmlfiles)
|
||||
when = self.when
|
||||
inverse = False
|
||||
elif self.when_not is not undefined:
|
||||
when = self.when_not
|
||||
inverse = True
|
||||
else:
|
||||
msg = f'the suffix has an invalid attribute "{self.attribute_name}", when and when_not cannot set together'
|
||||
raise DictConsistencyError
|
||||
params = {None: [self.attribute_name, self.get_suffix()],
|
||||
"when": when,
|
||||
"inverse": inverse,
|
||||
}
|
||||
return {
|
||||
"function": "variable_to_property",
|
||||
"params": params,
|
||||
"help": "variable_to_property",
|
||||
}
|
||||
|
||||
|
||||
class IndexCalculation(Calculation):
|
||||
attribute_name: Literal["default"]
|
||||
attribute_name: Literal["default", "choice", "dynamic"]
|
||||
|
||||
def to_function(
|
||||
self,
|
||||
objectspace,
|
||||
) -> dict:
|
||||
if self.path not in objectspace.followers:
|
||||
msg = f'the variable "{self.path}" is not a follower, so cannot have index type for "{self.attribute_name}"'
|
||||
raise DictConsistencyError(msg, 60, self.xmlfiles)
|
||||
return {
|
||||
"function": "calc_value",
|
||||
"params": {None: [{"type": "index"}]},
|
||||
|
|
@ -392,58 +682,76 @@ CALCULATION_TYPES = {
|
|||
"suffix": SuffixCalculation,
|
||||
"index": IndexCalculation,
|
||||
}
|
||||
BASETYPE_CALC = Union[StrictBool, StrictInt, StrictFloat, StrictStr, None, Calculation]
|
||||
CALCULATION_PROPERTY_TYPES = {
|
||||
"jinja": JinjaCalculation,
|
||||
"variable": VariablePropertyCalculation,
|
||||
"information": InformationCalculation,
|
||||
"suffix": SuffixPropertyCalculation,
|
||||
"index": IndexCalculation,
|
||||
}
|
||||
BASETYPE_CALC = Union[StrictBool, StrictInt, StrictFloat, StrictStr, Calculation, None]
|
||||
|
||||
|
||||
class Family(BaseModel):
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
type: Literal["family", "leadership", "dynamic"] = "family"
|
||||
path: str
|
||||
help: Optional[str] = None
|
||||
mode: Optional[str] = None
|
||||
hidden: Union[bool, Calculation] = False
|
||||
disabled: Union[bool, Calculation] = False
|
||||
namespace: Optional[str]
|
||||
version: str
|
||||
xmlfiles: List[str] = []
|
||||
path: str
|
||||
|
||||
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
|
||||
|
||||
|
||||
class Dynamic(Family):
|
||||
variable: str
|
||||
# None only for format 1.0
|
||||
variable: str = None
|
||||
dynamic: Union[List[Union[StrictStr, Calculation]], Calculation]
|
||||
|
||||
|
||||
class _Variable(BaseModel):
|
||||
class Variable(BaseModel):
|
||||
# type will be set dynamically in `annotator/value.py`, default is None
|
||||
type: str = None
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
default: Union[List[BASETYPE_CALC], BASETYPE_CALC] = None
|
||||
choices: Optional[Union[List[BASETYPE_CALC], Calculation]] = None
|
||||
regexp: Optional[str] = None
|
||||
params: Optional[List[Param]] = None
|
||||
validators: Optional[List[Calculation]] = None
|
||||
multi: bool = False
|
||||
multi: Optional[bool] = None
|
||||
unique: Optional[bool] = None
|
||||
help: Optional[str] = None
|
||||
hidden: Union[bool, Calculation] = False
|
||||
disabled: Union[bool, Calculation] = False
|
||||
mandatory: Union[None, bool, Calculation] = True
|
||||
empty: Union[None, bool, Calculation] = True
|
||||
auto_save: bool = False
|
||||
mode: Optional[str] = None
|
||||
test: Optional[list] = None
|
||||
xmlfiles: List[str] = []
|
||||
examples: Optional[list] = None
|
||||
path: str
|
||||
namespace: Optional[str]
|
||||
version: str
|
||||
path_prefix: Optional[str]
|
||||
xmlfiles: List[str] = []
|
||||
|
||||
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
|
||||
|
||||
|
||||
class Choice(_Variable):
|
||||
type: Literal["choice"] = "choice"
|
||||
choices: Union[List[BASETYPE_CALC], Calculation]
|
||||
|
||||
|
||||
class SymLink(BaseModel):
|
||||
name: str
|
||||
type: Literal["symlink"] = "symlink"
|
||||
opt: _Variable
|
||||
xmlfiles: List[str] = []
|
||||
name: str
|
||||
path: str
|
||||
opt: Variable
|
||||
namespace: Optional[str]
|
||||
version: str
|
||||
path_prefix: Optional[str]
|
||||
xmlfiles: List[str] = []
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
|
|
|||
|
|
@ -1,527 +0,0 @@
|
|||
"""Manage path to find objects
|
||||
|
||||
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-2024
|
||||
|
||||
distribued with GPL-2 or later license
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
"""
|
||||
from typing import List
|
||||
from .i18n import _
|
||||
from .error import DictConsistencyError
|
||||
from .utils import normalize_family
|
||||
|
||||
|
||||
class Path:
|
||||
"""Helper class to handle the `path` attribute.
|
||||
|
||||
sample: path="creole.general.condition"
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
rougailconfig: "RougailConfig",
|
||||
) -> None:
|
||||
self.variables = {}
|
||||
self.families = {}
|
||||
# self.names = {}
|
||||
self.full_paths_families = {}
|
||||
self.full_paths_variables = {}
|
||||
self.full_dyn_paths_families = {}
|
||||
self.valid_enums = {}
|
||||
self.variable_namespace = rougailconfig["variable_namespace"]
|
||||
self.providers = {}
|
||||
self.suppliers = {}
|
||||
self.list_conditions = {}
|
||||
self.suffix = rougailconfig["suffix"]
|
||||
self.index = 0
|
||||
|
||||
def set_path_prefix(self, prefix: str) -> None:
|
||||
self._path_prefix = prefix
|
||||
if prefix:
|
||||
if None in self.full_paths_families:
|
||||
raise DictConsistencyError(
|
||||
_(f'prefix "{prefix}" cannot be set if a prefix "None" exists'),
|
||||
39,
|
||||
None,
|
||||
)
|
||||
else:
|
||||
for old_prefix in self.full_paths_families:
|
||||
if old_prefix != None:
|
||||
raise DictConsistencyError(
|
||||
_(f"no prefix cannot be set if a prefix exists"), 84, None
|
||||
)
|
||||
if prefix in self.full_paths_families:
|
||||
raise DictConsistencyError(_(f'prefix "{prefix}" already exists'), 83, None)
|
||||
self.full_paths_families[prefix] = {}
|
||||
self.full_paths_variables[prefix] = {}
|
||||
self.valid_enums[prefix] = {}
|
||||
self.providers[prefix] = {}
|
||||
self.suppliers[prefix] = {}
|
||||
self.list_conditions[prefix] = {}
|
||||
|
||||
def has_path_prefix(self) -> bool:
|
||||
return None not in self.full_paths_families
|
||||
|
||||
def get_path_prefixes(self) -> list:
|
||||
return list(self.full_paths_families)
|
||||
|
||||
def get_path_prefix(self) -> str:
|
||||
return self._path_prefix
|
||||
|
||||
# Family
|
||||
def add_family(
|
||||
self,
|
||||
namespace: str,
|
||||
subpath: str,
|
||||
variableobj: str,
|
||||
is_dynamic: str,
|
||||
force_path_prefix: str = None,
|
||||
) -> str: # pylint: disable=C0111
|
||||
"""Add a new family"""
|
||||
if force_path_prefix is None:
|
||||
force_path_prefix = self._path_prefix
|
||||
path = subpath + "." + variableobj.name
|
||||
if namespace == self.variable_namespace:
|
||||
if variableobj.name in self.full_paths_families[force_path_prefix]:
|
||||
msg = _(f'Duplicate family name "{variableobj.name}"')
|
||||
raise DictConsistencyError(msg, 55, variableobj.xmlfiles)
|
||||
self.full_paths_families[force_path_prefix][variableobj.name] = path
|
||||
if is_dynamic:
|
||||
if subpath in self.full_dyn_paths_families:
|
||||
dyn_subpath = self.full_dyn_paths_families[subpath]
|
||||
else:
|
||||
dyn_subpath = subpath
|
||||
self.full_dyn_paths_families[
|
||||
path
|
||||
] = f"{dyn_subpath}.{variableobj.name}{{suffix}}"
|
||||
if path in self.families:
|
||||
msg = _(f'Duplicate family name "{path}"')
|
||||
raise DictConsistencyError(msg, 37, variableobj.xmlfiles)
|
||||
if path in self.variables:
|
||||
msg = _(f'A variable and a family has the same path "{path}"')
|
||||
raise DictConsistencyError(msg, 56, variableobj.xmlfiles)
|
||||
self.families[path] = dict(
|
||||
name=path,
|
||||
namespace=namespace,
|
||||
variableobj=variableobj,
|
||||
)
|
||||
self.set_name(variableobj, "optiondescription_")
|
||||
variableobj.path = path
|
||||
variableobj.path_prefix = force_path_prefix
|
||||
|
||||
def get_family(
|
||||
self,
|
||||
path: str,
|
||||
current_namespace: str,
|
||||
path_prefix: str,
|
||||
allow_variable_namespace: bool = False,
|
||||
) -> "Family": # pylint: disable=C0111
|
||||
"""Get a family"""
|
||||
if (
|
||||
current_namespace == self.variable_namespace or allow_variable_namespace
|
||||
) and path in self.full_paths_families[path_prefix]:
|
||||
path = self.full_paths_families[path_prefix][path]
|
||||
elif allow_variable_namespace and path_prefix:
|
||||
path = f"{path_prefix}.{path}"
|
||||
if path not in self.families:
|
||||
raise DictConsistencyError(_(f'unknown option "{path}"'), 42, [])
|
||||
dico = self.families[path]
|
||||
if current_namespace != dico["namespace"] and (
|
||||
not allow_variable_namespace or current_namespace != self.variable_namespace
|
||||
):
|
||||
msg = _(
|
||||
f'A family located in the "{dico["namespace"]}" namespace '
|
||||
f'shall not be used in the "{current_namespace}" namespace'
|
||||
)
|
||||
raise DictConsistencyError(msg, 38, [])
|
||||
return dico["variableobj"]
|
||||
|
||||
def _get_dyn_path(
|
||||
self,
|
||||
subpath: str,
|
||||
name: bool,
|
||||
) -> str:
|
||||
if subpath in self.full_dyn_paths_families:
|
||||
subpath = self.full_dyn_paths_families[subpath]
|
||||
path = f"{subpath}.{name}{{suffix}}"
|
||||
else:
|
||||
path = f"{subpath}.{name}"
|
||||
return path
|
||||
|
||||
def set_provider(
|
||||
self,
|
||||
variableobj,
|
||||
name,
|
||||
family,
|
||||
):
|
||||
if not hasattr(variableobj, "provider"):
|
||||
return
|
||||
p_name = "provider:" + variableobj.provider
|
||||
if "." in name:
|
||||
msg = f'provider "{p_name}" not allowed in extra'
|
||||
raise DictConsistencyError(msg, 82, variableobj.xmlfiles)
|
||||
if p_name in self.providers[variableobj.path_prefix]:
|
||||
msg = f'provider "{p_name}" declare multiple time'
|
||||
raise DictConsistencyError(msg, 79, variableobj.xmlfiles)
|
||||
self.providers[variableobj.path_prefix][p_name] = {
|
||||
"path": self._get_dyn_path(
|
||||
family,
|
||||
name,
|
||||
),
|
||||
"option": variableobj,
|
||||
}
|
||||
|
||||
def get_provider(
|
||||
self,
|
||||
name: str,
|
||||
path_prefix: str = None,
|
||||
) -> "self.objectspace.variable":
|
||||
return self.providers[path_prefix][name]["option"]
|
||||
|
||||
def get_providers_path(self, path_prefix=None):
|
||||
if path_prefix:
|
||||
return {
|
||||
name: option["path"].split(".", 1)[-1]
|
||||
for name, option in self.providers[path_prefix].items()
|
||||
}
|
||||
return {
|
||||
name: option["path"] for name, option in self.providers[path_prefix].items()
|
||||
}
|
||||
|
||||
def set_supplier(
|
||||
self,
|
||||
variableobj,
|
||||
name,
|
||||
family,
|
||||
):
|
||||
if not hasattr(variableobj, "supplier"):
|
||||
return
|
||||
s_name = "supplier:" + variableobj.supplier
|
||||
if "." in name:
|
||||
msg = f'supplier "{s_name}" not allowed in extra'
|
||||
raise DictConsistencyError(msg, 82, variableobj.xmlfiles)
|
||||
if s_name in self.suppliers[variableobj.path_prefix]:
|
||||
msg = f'supplier "{s_name}" declare multiple time'
|
||||
raise DictConsistencyError(msg, 79, variableobj.xmlfiles)
|
||||
self.suppliers[variableobj.path_prefix][s_name] = {
|
||||
"path": self._get_dyn_path(family, name),
|
||||
"option": variableobj,
|
||||
}
|
||||
|
||||
def get_supplier(
|
||||
self,
|
||||
name: str,
|
||||
path_prefix: str = None,
|
||||
) -> "self.objectspace.variable":
|
||||
return self.suppliers[path_prefix][name]["option"]
|
||||
|
||||
def get_suppliers_path(self, path_prefix=None):
|
||||
if path_prefix:
|
||||
return {
|
||||
name: option["path"].split(".", 1)[-1]
|
||||
for name, option in self.suppliers[path_prefix].items()
|
||||
}
|
||||
return {
|
||||
name: option["path"] for name, option in self.suppliers[path_prefix].items()
|
||||
}
|
||||
|
||||
# Variable
|
||||
def add_variable(
|
||||
self, # pylint: disable=R0913
|
||||
namespace: str,
|
||||
subpath: str,
|
||||
variableobj: "self.objectspace.variable",
|
||||
is_dynamic: bool = False,
|
||||
is_leader: bool = False,
|
||||
force_path_prefix: str = None,
|
||||
) -> str: # pylint: disable=C0111
|
||||
"""Add a new variable (with path)"""
|
||||
if force_path_prefix is None:
|
||||
force_path_prefix = self._path_prefix
|
||||
path = subpath + "." + variableobj.name
|
||||
if namespace == self.variable_namespace:
|
||||
self.full_paths_variables[force_path_prefix][variableobj.name] = path
|
||||
if path in self.families:
|
||||
msg = _(f'A family and a variable has the same path "{path}"')
|
||||
raise DictConsistencyError(msg, 57, variableobj.xmlfiles)
|
||||
if is_leader:
|
||||
leader = subpath
|
||||
else:
|
||||
leader = None
|
||||
self.variables[path] = dict(
|
||||
name=path,
|
||||
family=subpath,
|
||||
leader=leader,
|
||||
is_dynamic=is_dynamic,
|
||||
variableobj=variableobj,
|
||||
)
|
||||
variableobj.path = path
|
||||
variableobj.path_prefix = force_path_prefix
|
||||
self.set_name(variableobj, "option_")
|
||||
|
||||
def set_name(
|
||||
self,
|
||||
variableobj,
|
||||
option_prefix,
|
||||
):
|
||||
self.index += 1
|
||||
variableobj.reflector_name = f"{option_prefix}{self.index}{self.suffix}"
|
||||
|
||||
def get_variable(
|
||||
self,
|
||||
name: str,
|
||||
namespace: str,
|
||||
xmlfiles: List[str] = [],
|
||||
allow_variable_namespace: bool = False,
|
||||
force_path_prefix: str = None,
|
||||
add_path_prefix: bool = False,
|
||||
) -> "Variable": # pylint: disable=C0111
|
||||
"""Get variable object from a path"""
|
||||
if force_path_prefix is None:
|
||||
force_path_prefix = self._path_prefix
|
||||
try:
|
||||
variable, suffix = self._get_variable(
|
||||
name,
|
||||
namespace,
|
||||
with_suffix=True,
|
||||
xmlfiles=xmlfiles,
|
||||
path_prefix=force_path_prefix,
|
||||
add_path_prefix=add_path_prefix,
|
||||
)
|
||||
except DictConsistencyError as err:
|
||||
if (
|
||||
not allow_variable_namespace
|
||||
or err.errno != 42
|
||||
or namespace == self.variable_namespace
|
||||
):
|
||||
raise err from err
|
||||
variable, suffix = self._get_variable(
|
||||
name,
|
||||
self.variable_namespace,
|
||||
with_suffix=True,
|
||||
xmlfiles=xmlfiles,
|
||||
path_prefix=force_path_prefix,
|
||||
)
|
||||
if suffix:
|
||||
raise DictConsistencyError(_(f"{name} is a dynamic variable"), 36, [])
|
||||
return variable["variableobj"]
|
||||
|
||||
def get_variable_family_path(
|
||||
self,
|
||||
name: str,
|
||||
namespace: str,
|
||||
xmlfiles: List[str] = False,
|
||||
force_path_prefix: str = None,
|
||||
) -> str: # pylint: disable=C0111
|
||||
"""Get the full path of a family"""
|
||||
if force_path_prefix is None:
|
||||
force_path_prefix = self._path_prefix
|
||||
return self._get_variable(
|
||||
name,
|
||||
namespace,
|
||||
xmlfiles=xmlfiles,
|
||||
path_prefix=force_path_prefix,
|
||||
)["family"]
|
||||
|
||||
def get_variable_with_suffix(
|
||||
self,
|
||||
name: str,
|
||||
current_namespace: str,
|
||||
xmlfiles: List[str],
|
||||
path_prefix: str,
|
||||
) -> str: # pylint: disable=C0111
|
||||
"""get full path of a variable"""
|
||||
try:
|
||||
dico, suffix = self._get_variable(
|
||||
name,
|
||||
current_namespace,
|
||||
with_suffix=True,
|
||||
xmlfiles=xmlfiles,
|
||||
path_prefix=path_prefix,
|
||||
add_path_prefix=True,
|
||||
)
|
||||
except DictConsistencyError as err:
|
||||
if err.errno != 42 or current_namespace == self.variable_namespace:
|
||||
raise err from err
|
||||
dico, suffix = self._get_variable(
|
||||
name,
|
||||
self.variable_namespace,
|
||||
with_suffix=True,
|
||||
xmlfiles=xmlfiles,
|
||||
path_prefix=path_prefix,
|
||||
add_path_prefix=True,
|
||||
)
|
||||
namespace = dico["variableobj"].namespace
|
||||
if (
|
||||
namespace not in [self.variable_namespace, "services"]
|
||||
and current_namespace != "services"
|
||||
and current_namespace != namespace
|
||||
):
|
||||
msg = _(
|
||||
f'A variable located in the "{namespace}" namespace shall not be used '
|
||||
f'in the "{current_namespace}" namespace'
|
||||
)
|
||||
raise DictConsistencyError(msg, 41, xmlfiles)
|
||||
return dico["variableobj"], suffix
|
||||
|
||||
def path_is_defined(
|
||||
self,
|
||||
path: str,
|
||||
namespace: str,
|
||||
force_path_prefix: str = None,
|
||||
) -> str: # pylint: disable=C0111
|
||||
"""The path is a valid path"""
|
||||
if namespace == self.variable_namespace:
|
||||
if force_path_prefix is None:
|
||||
force_path_prefix = self._path_prefix
|
||||
return path in self.full_paths_variables[force_path_prefix]
|
||||
return path in self.variables
|
||||
|
||||
def get_path(
|
||||
self,
|
||||
path: str,
|
||||
namespace: str,
|
||||
) -> str:
|
||||
if namespace == self.variable_namespace:
|
||||
if path not in self.full_paths_variables[self._path_prefix]:
|
||||
return None
|
||||
path = self.full_paths_variables[self._path_prefix][path]
|
||||
else:
|
||||
path = f"{self._path_prefix}.{path}"
|
||||
return path
|
||||
|
||||
def is_dynamic(self, variableobj) -> bool:
|
||||
"""This variable is in dynamic family"""
|
||||
return self._get_variable(
|
||||
variableobj.path,
|
||||
variableobj.namespace,
|
||||
path_prefix=variableobj.path_prefix,
|
||||
)["is_dynamic"]
|
||||
|
||||
def is_leader(self, variableobj): # pylint: disable=C0111
|
||||
"""Is the variable is a leader"""
|
||||
path = variableobj.path
|
||||
variable = self._get_variable(
|
||||
path,
|
||||
variableobj.namespace,
|
||||
path_prefix=variableobj.path_prefix,
|
||||
)
|
||||
if not variable["leader"]:
|
||||
return False
|
||||
leadership = self.get_family(
|
||||
variable["leader"],
|
||||
variableobj.namespace,
|
||||
path_prefix=variableobj.path_prefix,
|
||||
)
|
||||
return next(iter(leadership.variable.values())).path == path
|
||||
|
||||
def is_follower(self, variableobj) -> bool:
|
||||
"""Is the variable is a follower"""
|
||||
variable = self._get_variable(
|
||||
variableobj.path,
|
||||
variableobj.namespace,
|
||||
path_prefix=variableobj.path_prefix,
|
||||
)
|
||||
if not variable["leader"]:
|
||||
return False
|
||||
leadership = self.get_family(
|
||||
variable["leader"],
|
||||
variableobj.namespace,
|
||||
path_prefix=variableobj.path_prefix,
|
||||
)
|
||||
return next(iter(leadership.variable.values())).path != variableobj.path
|
||||
|
||||
def get_leader(self, variableobj) -> str:
|
||||
variable = self._get_variable(
|
||||
variableobj.path,
|
||||
variableobj.namespace,
|
||||
path_prefix=variableobj.path_prefix,
|
||||
)
|
||||
if not variable["leader"]:
|
||||
raise Exception(f"cannot find leader for {variableobj.path}")
|
||||
leadership = self.get_family(
|
||||
variable["leader"],
|
||||
variableobj.namespace,
|
||||
path_prefix=variableobj.path_prefix,
|
||||
)
|
||||
return next(iter(leadership.variable.values()))
|
||||
|
||||
def _get_variable(
|
||||
self,
|
||||
path: str,
|
||||
namespace: str,
|
||||
with_suffix: bool = False,
|
||||
xmlfiles: List[str] = [],
|
||||
path_prefix: str = None,
|
||||
add_path_prefix: bool = False,
|
||||
) -> str:
|
||||
if namespace == self.variable_namespace:
|
||||
if path in self.full_paths_variables[path_prefix]:
|
||||
path = self.full_paths_variables[path_prefix][path]
|
||||
else:
|
||||
if with_suffix:
|
||||
for var_name, full_path in self.full_paths_variables[
|
||||
path_prefix
|
||||
].items():
|
||||
if not path.startswith(var_name):
|
||||
continue
|
||||
variable = self._get_variable(
|
||||
full_path, namespace, path_prefix=path_prefix
|
||||
)
|
||||
if not variable["is_dynamic"]:
|
||||
continue
|
||||
return variable, path[len(var_name) :]
|
||||
if path_prefix and add_path_prefix:
|
||||
path = f"{path_prefix}.{path}"
|
||||
elif path_prefix and add_path_prefix:
|
||||
path = f"{path_prefix}.{path}"
|
||||
# FIXME with_suffix and variable in extra?
|
||||
if path not in self.variables:
|
||||
raise DictConsistencyError(_(f'unknown option "{path}"'), 42, xmlfiles)
|
||||
if with_suffix:
|
||||
return self.variables[path], None
|
||||
return self.variables[path]
|
||||
|
||||
def set_valid_enums(
|
||||
self,
|
||||
path,
|
||||
values,
|
||||
path_prefix,
|
||||
):
|
||||
self.valid_enums[path_prefix][path] = values
|
||||
|
||||
def has_valid_enums(
|
||||
self,
|
||||
path: str,
|
||||
path_prefix: str,
|
||||
) -> bool:
|
||||
return path in self.valid_enums[path_prefix]
|
||||
|
||||
def get_valid_enums(
|
||||
self,
|
||||
path: str,
|
||||
path_prefix: str,
|
||||
):
|
||||
return self.valid_enums[path_prefix][path]
|
||||
|
|
@ -1,220 +0,0 @@
|
|||
"""load XML and YAML file from directory
|
||||
|
||||
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-2024
|
||||
|
||||
distribued with GPL-2 or later license
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
"""
|
||||
from typing import List
|
||||
from os.path import join, isfile
|
||||
from os import listdir
|
||||
|
||||
from lxml.etree import DTD, parse, XMLSyntaxError # pylint: disable=E0611
|
||||
from pykwalify.compat import yml
|
||||
from pykwalify.core import Core
|
||||
from pykwalify.errors import SchemaError
|
||||
|
||||
|
||||
from .i18n import _
|
||||
from .error import DictConsistencyError
|
||||
|
||||
|
||||
FORCE_SUBYAML = ["override"]
|
||||
SCHEMA_DATA = {}
|
||||
|
||||
|
||||
class Reflector:
|
||||
"""Helper class for loading the Creole XML file,
|
||||
parsing it, validating against the Creole DTD
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
rougailconfig: "RougailConfig",
|
||||
) -> None:
|
||||
"""Loads the Creole DTD
|
||||
|
||||
:raises IOError: if the DTD is not found
|
||||
|
||||
:param dtdfilename: the full filename of the Creole DTD
|
||||
"""
|
||||
dtdfilename = rougailconfig["dtdfilename"]
|
||||
yamlschema_filename = rougailconfig["yamlschema_filename"]
|
||||
if not isfile(dtdfilename):
|
||||
raise IOError(_(f"no such DTD file: {dtdfilename}"))
|
||||
with open(dtdfilename, "r") as dtdfd:
|
||||
self.dtd = DTD(dtdfd)
|
||||
if not isfile(yamlschema_filename):
|
||||
raise IOError(_(f"no such YAML Schema file: {yamlschema_filename}"))
|
||||
self.yamlschema_filename = yamlschema_filename
|
||||
self.schema_data = None
|
||||
|
||||
def load_dictionaries_from_folders(
|
||||
self,
|
||||
folders: List[str],
|
||||
just_doc: bool,
|
||||
):
|
||||
"""Loads all the dictionary files located in the folders' list
|
||||
|
||||
:param folders: list of full folder's name
|
||||
"""
|
||||
filenames = {}
|
||||
for folder in folders:
|
||||
for filename in listdir(folder):
|
||||
if filename.endswith(".xml"):
|
||||
ext = "xml"
|
||||
full_filename = join(folder, filename)
|
||||
elif filename.endswith(".yml"):
|
||||
ext = "yml"
|
||||
full_filename = join(folder, filename)
|
||||
else:
|
||||
continue
|
||||
if filename in filenames:
|
||||
raise DictConsistencyError(
|
||||
_(f"duplicate dictionary file name {filename}"),
|
||||
78,
|
||||
[filenames[filename][1], full_filename],
|
||||
)
|
||||
filenames[filename] = (ext, full_filename)
|
||||
if not filenames and not just_doc:
|
||||
raise DictConsistencyError(_("there is no dictionary file"), 77, folders)
|
||||
file_names = list(filenames.keys())
|
||||
file_names.sort()
|
||||
for filename in file_names:
|
||||
ext, filename = filenames[filename]
|
||||
if ext == "xml":
|
||||
yield self.load_xml_file(filename)
|
||||
else:
|
||||
yield self.load_yml_file(filename)
|
||||
|
||||
def load_xml_file(
|
||||
self,
|
||||
filename: str,
|
||||
):
|
||||
try:
|
||||
document = parse(filename)
|
||||
except XMLSyntaxError as err:
|
||||
raise DictConsistencyError(
|
||||
_(f"not a XML file: {err}"), 52, [filename]
|
||||
) from err
|
||||
if not self.dtd.validate(document):
|
||||
dtd_error = self.dtd.error_log.filter_from_errors()[0]
|
||||
msg = _(f"not a valid XML file: {dtd_error}")
|
||||
raise DictConsistencyError(msg, 43, [filename])
|
||||
return filename, document.getroot()
|
||||
|
||||
def load_yml_file(
|
||||
self,
|
||||
filename: str,
|
||||
):
|
||||
global SCHEMA_DATA
|
||||
if self.yamlschema_filename not in SCHEMA_DATA:
|
||||
with open(self.yamlschema_filename, "r") as fh:
|
||||
SCHEMA_DATA[self.yamlschema_filename] = yml.load(fh)
|
||||
try:
|
||||
document = Core(
|
||||
source_file=filename,
|
||||
schema_data=SCHEMA_DATA[self.yamlschema_filename],
|
||||
)
|
||||
except XMLSyntaxError as err:
|
||||
raise DictConsistencyError(
|
||||
_(f"not a XML file: {err}"), 52, [filename]
|
||||
) from err
|
||||
try:
|
||||
return filename, YParser(document.validate(raise_exception=True))
|
||||
except SchemaError as yaml_error:
|
||||
msg = _(f"not a valid YAML file: {yaml_error}")
|
||||
raise DictConsistencyError(msg, 43, [filename])
|
||||
|
||||
|
||||
class SubYAML:
|
||||
def __init__(self, key, value):
|
||||
if value is None:
|
||||
value = {}
|
||||
self.tag = key
|
||||
self.dico = value
|
||||
if "text" in value:
|
||||
self.text = value["text"]
|
||||
else:
|
||||
self.text = None
|
||||
if isinstance(value, list):
|
||||
self.attrib = {}
|
||||
else:
|
||||
self.attrib = {
|
||||
k: v
|
||||
for k, v in value.items()
|
||||
if not isinstance(v, list) and k not in FORCE_SUBYAML
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return f"<SubYAML {self.tag} at {id(self)}>"
|
||||
|
||||
def __iter__(self):
|
||||
if isinstance(self.dico, list):
|
||||
lists = []
|
||||
for dico in self.dico:
|
||||
for key, value in dico.items():
|
||||
if not isinstance(value, list):
|
||||
value = [value]
|
||||
lists.append((key, value))
|
||||
else:
|
||||
lists = []
|
||||
for key, values in self.dico.items():
|
||||
if key == "variables":
|
||||
for v in values:
|
||||
if "variable" in v:
|
||||
lists.append(("variable", v["variable"]))
|
||||
if "family" in v:
|
||||
lists.append(("family", v["family"]))
|
||||
else:
|
||||
lists.append((key, values))
|
||||
for key, values in lists:
|
||||
if key not in FORCE_SUBYAML and not isinstance(values, list):
|
||||
continue
|
||||
if values is None:
|
||||
values = [None]
|
||||
for value in values:
|
||||
yield SubYAML(key, value)
|
||||
|
||||
def __len__(self):
|
||||
length = 0
|
||||
for _ in self.__iter__():
|
||||
length += 1
|
||||
return length
|
||||
|
||||
|
||||
class YParser:
|
||||
def __init__(self, dico):
|
||||
self.dico = dico
|
||||
|
||||
def __iter__(self):
|
||||
for key, values in self.dico.items():
|
||||
if not isinstance(values, list):
|
||||
continue
|
||||
if key == "variables":
|
||||
yield SubYAML(key, values)
|
||||
else:
|
||||
for val in values:
|
||||
yield SubYAML(key, val)
|
||||
89
src/rougail/structural_commandline/annotator.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
"""Annotate to add specify attribute for tiramisu-cmdline
|
||||
|
||||
Silique (https://www.silique.fr)
|
||||
Copyright (C) 2024
|
||||
|
||||
distribued with GPL-2 or later license
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
"""
|
||||
from rougail.annotator.variable import Walk
|
||||
from rougail.utils import _
|
||||
from rougail.error import DictConsistencyError
|
||||
|
||||
class Annotator(Walk):
|
||||
"""Annotate value"""
|
||||
level = 80
|
||||
|
||||
def __init__(self, objectspace, *args) -> None:
|
||||
if not objectspace.paths:
|
||||
return
|
||||
self.alternative_names = {}
|
||||
self.objectspace = objectspace
|
||||
not_for_commandlines = []
|
||||
for family in self.get_families():
|
||||
if family.commandline:
|
||||
continue
|
||||
self.not_for_commandline(family)
|
||||
not_for_commandlines.append(family.path + '.')
|
||||
for variable in self.get_variables():
|
||||
if variable.type == 'symlink':
|
||||
continue
|
||||
variable_path = variable.path
|
||||
for family_path in not_for_commandlines:
|
||||
if variable_path.startswith(family_path):
|
||||
break
|
||||
else:
|
||||
if not variable.commandline:
|
||||
self.not_for_commandline(variable)
|
||||
else:
|
||||
self.manage_alternative_name(variable)
|
||||
self.manage_negative_description(variable)
|
||||
|
||||
def not_for_commandline(self, variable) -> None:
|
||||
self.objectspace.properties.add(variable.path, 'not_for_commandline', True)
|
||||
|
||||
def manage_alternative_name(self, variable) -> None:
|
||||
if not variable.alternative_name:
|
||||
return
|
||||
alternative_name = variable.alternative_name
|
||||
variable_path = variable.path
|
||||
all_letters = ''
|
||||
for letter in alternative_name:
|
||||
all_letters += letter
|
||||
if all_letters == 'h':
|
||||
msg = _(f'alternative_name "{alternative_name}" conflict with "--help"')
|
||||
raise DictConsistencyError(msg, 202, variable.xmlfiles)
|
||||
if all_letters in self.alternative_names:
|
||||
msg = _(f'conflict alternative_name "{alternative_name}": "{variable_path}" and "{self.alternative_names[all_letters]}"')
|
||||
raise DictConsistencyError(msg, 202, variable.xmlfiles)
|
||||
|
||||
self.alternative_names[alternative_name] = variable_path
|
||||
if '.' not in variable_path:
|
||||
path = alternative_name
|
||||
else:
|
||||
path = variable_path.rsplit('.', 1)[0] + '.' + alternative_name
|
||||
self.objectspace.add_variable(alternative_name, {'type': 'symlink', 'path': path, 'opt': variable}, variable.xmlfiles, False, False, variable.version)
|
||||
|
||||
def manage_negative_description(self, variable) -> None:
|
||||
if not variable.negative_description:
|
||||
if variable.type == 'boolean' and not self.objectspace.add_extra_options:
|
||||
raise DictConsistencyError(_(f'negative_description is mandatory for boolean variable, but "{variable.path}" hasn\'t'), 200, variable.xmlfiles)
|
||||
return
|
||||
if variable.type != 'boolean':
|
||||
raise DictConsistencyError(_(f'negative_description is only available for boolean variable, but "{variable.path}" is "{variable.type}"'), 201, variable.xmlfiles)
|
||||
self.objectspace.informations.add(
|
||||
variable.path, "negative_description", variable.negative_description
|
||||
)
|
||||
42
src/rougail/structural_commandline/config.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""
|
||||
Config file for Rougail-structural_commandline
|
||||
|
||||
Silique (https://www.silique.fr)
|
||||
Copyright (C) 2024
|
||||
|
||||
distribued with GPL-2 or later license
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
"""
|
||||
def get_rougail_config(*,
|
||||
backward_compatibility=True,
|
||||
) -> dict:
|
||||
options = """
|
||||
structural_commandline:
|
||||
description: Configuration rougail-structural_commandline
|
||||
commandline: false
|
||||
add_extra_options:
|
||||
description: Add extra options to tiramisu-cmdline-parser
|
||||
default: true
|
||||
"""
|
||||
return {'name': 'exporter',
|
||||
'process': 'structural',
|
||||
'options': options,
|
||||
'level': 20,
|
||||
}
|
||||
|
||||
|
||||
__all__ = ('get_rougail_config')
|
||||
|
||||
36
src/rougail/structural_commandline/object_model.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""Annotate to add specify attribute for tiramisu-cmdline
|
||||
|
||||
Silique (https://www.silique.fr)
|
||||
Copyright (C) 2024
|
||||
|
||||
distribued with GPL-2 or later license
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
"""
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Variable(BaseModel):
|
||||
alternative_name: Optional[str]=None
|
||||
commandline: bool=True
|
||||
negative_description: Optional[str]=None
|
||||
|
||||
|
||||
class Family(BaseModel):
|
||||
commandline: bool=True
|
||||
|
||||
|
||||
__all__ = ('Variable', 'Family')
|
||||
|
|
@ -28,12 +28,115 @@ along with this program; if not, write to the Free Software
|
|||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
"""
|
||||
try:
|
||||
from tiramisu4 import DynOptionDescription
|
||||
from tiramisu5 import DynOptionDescription, calc_value
|
||||
except ModuleNotFoundError:
|
||||
from tiramisu import DynOptionDescription
|
||||
from tiramisu import DynOptionDescription, calc_value
|
||||
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
|
||||
from jinja2 import StrictUndefined, DictLoader
|
||||
from jinja2.sandbox import SandboxedEnvironment
|
||||
from rougail.object_model import CONVERT_OPTION
|
||||
from rougail.error import display_xmlfiles
|
||||
from tiramisu.error import ValueWarning, ConfigError
|
||||
from .utils import normalize_family
|
||||
|
||||
|
||||
global func
|
||||
dict_env = {}
|
||||
ENV = SandboxedEnvironment(loader=DictLoader(dict_env), undefined=StrictUndefined)
|
||||
func = ENV.filters
|
||||
ENV.compile_templates('jinja_caches', zip=None)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def rougail_calc_value(*args, __default_value=None, **kwargs):
|
||||
values = calc_value(*args, **kwargs)
|
||||
if __default_value is not None and values in [None, []]:
|
||||
return __default_value
|
||||
return values
|
||||
|
||||
|
||||
def jinja_to_function(__internal_variable, __internal_attribute, __internal_jinja, __internal_type, __internal_multi, __internal_files, __default_value=None, **kwargs):
|
||||
global ENV, CONVERT_OPTION
|
||||
kw = {}
|
||||
for key, value in kwargs.items():
|
||||
if '.' in key:
|
||||
c_kw = kw
|
||||
path, var = key.rsplit('.', 1)
|
||||
for subkey in path.split('.'):
|
||||
c_kw = c_kw.setdefault(subkey, {})
|
||||
c_kw[var] = value
|
||||
else:
|
||||
if key in kw:
|
||||
raise ConfigError(f'internal error, multi key for "{key}" in jinja_to_function')
|
||||
kw[key] = value
|
||||
try:
|
||||
values = ENV.get_template(__internal_jinja).render(kw, **func).strip()
|
||||
except Exception as err:
|
||||
raise ConfigError(f'cannot calculating "{__internal_attribute}" attribute for variable "{__internal_variable}" in {display_xmlfiles(__internal_files)}: {err}') from err
|
||||
convert = CONVERT_OPTION[__internal_type].get('func', str)
|
||||
if __internal_multi:
|
||||
values = [convert(val) for val in values.split('\n') if val != ""]
|
||||
if not values and __default_value is not None:
|
||||
return __default_value
|
||||
return values
|
||||
try:
|
||||
values = convert(values)
|
||||
except Exception as err:
|
||||
raise ConfigError(f'cannot converting "{__internal_attribute}" attribute for variable "{__internal_variable}" in {display_xmlfiles(__internal_files)}: {err}') from err
|
||||
values = values if values != '' and values != 'None' else None
|
||||
if values is None and __default_value is not None:
|
||||
return __default_value
|
||||
return values
|
||||
|
||||
|
||||
def variable_to_property(prop, value, when, inverse):
|
||||
if inverse:
|
||||
is_match = value != when
|
||||
else:
|
||||
is_match = value == when
|
||||
return prop if is_match else None
|
||||
|
||||
|
||||
def jinja_to_property(prop, when, inverse, **kwargs):
|
||||
value = func['jinja_to_function'](**kwargs)
|
||||
return func['variable_to_property'](prop, value is not None, when, inverse)
|
||||
|
||||
|
||||
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:
|
||||
if warnings_only:
|
||||
raise ValueWarning(value)
|
||||
else:
|
||||
raise ValueError(value)
|
||||
|
||||
|
||||
func['calc_value'] = rougail_calc_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
|
||||
|
||||
|
||||
class ConvertDynOptionDescription(DynOptionDescription):
|
||||
"""Suffix could be an integer, we should convert it in str
|
||||
Suffix could also contain invalid character, so we should "normalize" it
|
||||
|
|
@ -58,3 +161,12 @@ class ConvertDynOptionDescription(DynOptionDescription):
|
|||
if "{{ suffix }}" in name:
|
||||
return name.replace("{{ suffix }}", path_suffix)
|
||||
return name + path_suffix
|
||||
|
||||
def impl_get_display_name(
|
||||
self,
|
||||
subconfig,
|
||||
) -> str:
|
||||
display = super().impl_get_display_name(subconfig)
|
||||
if "{{ suffix }}" in display:
|
||||
return display.replace("{{ suffix }}", self.convert_suffix_to_path(self.get_suffixes(subconfig)[-1]))
|
||||
return display
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@ 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 typing import Optional, Union
|
||||
from json import dumps
|
||||
from os.path import isfile, basename
|
||||
|
||||
from .i18n import _
|
||||
from .error import DictConsistencyError
|
||||
from .error import DictConsistencyError, VariableCalculationDependencyError
|
||||
from .utils import normalize_family
|
||||
from .object_model import Calculation, CONVERT_OPTION
|
||||
|
||||
|
|
@ -59,120 +59,47 @@ class TiramisuReflector:
|
|||
objectspace,
|
||||
funcs_paths,
|
||||
):
|
||||
self.rougailconfig = objectspace.rougailconfig
|
||||
self.jinja_added = False
|
||||
self.informations_idx = -1
|
||||
self.reflector_objects = {}
|
||||
self.text = {
|
||||
"header": [],
|
||||
"option": [],
|
||||
}
|
||||
if self.rougailconfig["export_with_import"]:
|
||||
if self.rougailconfig["internal_functions"]:
|
||||
for func in self.rougailconfig["internal_functions"]:
|
||||
self.objectspace = objectspace
|
||||
if self.objectspace.export_with_import:
|
||||
if self.objectspace.internal_functions:
|
||||
for func in self.objectspace.internal_functions:
|
||||
self.text["header"].append(f"func[func] = func")
|
||||
self.text["header"].extend(
|
||||
[
|
||||
"from tiramisu import *",
|
||||
"from tiramisu.setting import ALLOWED_LEADER_PROPERTIES",
|
||||
"from re import compile as re_compile",
|
||||
]
|
||||
)
|
||||
if self.objectspace.export_with_import:
|
||||
self.text["header"].extend(
|
||||
[
|
||||
"from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription"
|
||||
]
|
||||
)
|
||||
for mode in self.rougailconfig["modes_level"]:
|
||||
self.text["header"].append(f'ALLOWED_LEADER_PROPERTIES.add("{mode}")')
|
||||
if funcs_paths:
|
||||
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",
|
||||
"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)",
|
||||
]
|
||||
)
|
||||
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}')")
|
||||
self.objectspace = objectspace
|
||||
self.text["header"].append(f"load_functions('{funcs_path}')")
|
||||
if self.objectspace.export_with_import:
|
||||
for mode in self.objectspace.modes_level:
|
||||
self.text["header"].append(f'ALLOWED_LEADER_PROPERTIES.add("{mode}")')
|
||||
self.make_tiramisu_objects()
|
||||
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_support(self):
|
||||
if not self.jinja_added:
|
||||
self.text["header"].extend(
|
||||
[
|
||||
"from jinja2 import StrictUndefined, DictLoader",
|
||||
"from jinja2.sandbox import SandboxedEnvironment",
|
||||
"from rougail 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:",
|
||||
" c_kw = kw",
|
||||
" path, var = key.rsplit('.', 1)",
|
||||
" for subkey in path.split('.'):",
|
||||
" c_kw = c_kw.setdefault(subkey, {})",
|
||||
" c_kw[var] = value",
|
||||
" else:",
|
||||
" kw[key] = value",
|
||||
" 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()]",
|
||||
" 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:",
|
||||
" 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}")
|
||||
|
||||
|
|
@ -181,54 +108,51 @@ class TiramisuReflector:
|
|||
baseelt = BaseElt()
|
||||
self.objectspace.reflector_names[
|
||||
baseelt.path
|
||||
] = f'option_0{self.rougailconfig["suffix"]}'
|
||||
] = f"option_0{self.objectspace.suffix}"
|
||||
basefamily = Family(
|
||||
baseelt,
|
||||
self,
|
||||
)
|
||||
# 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,
|
||||
)
|
||||
else:
|
||||
path_prefixes = self.objectspace.paths.get_path_prefixes()
|
||||
for path_prefix in path_prefixes:
|
||||
space = self.objectspace.space.variables[path_prefix]
|
||||
self.set_name(space)
|
||||
baseprefix = Family(
|
||||
space,
|
||||
for elt in self.objectspace.paths.get():
|
||||
if elt.path in self.objectspace.families:
|
||||
Family(
|
||||
elt,
|
||||
self,
|
||||
)
|
||||
basefamily.add(baseprefix)
|
||||
for elt in self.reorder_family(space):
|
||||
self.populate_family(
|
||||
baseprefix,
|
||||
elt,
|
||||
)
|
||||
if not hasattr(baseprefix.elt, "information"):
|
||||
baseprefix.elt.information = self.objectspace.information(
|
||||
baseprefix.elt.xmlfiles
|
||||
)
|
||||
for key, value in self.objectspace.paths.get_providers_path(
|
||||
path_prefix
|
||||
).items():
|
||||
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.rougailconfig["base_option_name"])
|
||||
baseelt.description = self.rougailconfig["base_option_name"]
|
||||
else:
|
||||
Variable(
|
||||
elt,
|
||||
self,
|
||||
)
|
||||
# else:
|
||||
# path_prefixes = self.objectspace.paths.get_path_prefixes()
|
||||
# for path_prefix in path_prefixes:
|
||||
# space = self.objectspace.space.variables[path_prefix]
|
||||
# self.set_name(space)
|
||||
# baseprefix = Family(
|
||||
# space,
|
||||
# self,
|
||||
# )
|
||||
# basefamily.add(baseprefix)
|
||||
# for elt in self.reorder_family(space):
|
||||
# self.populate_family(
|
||||
# baseprefix,
|
||||
# elt,
|
||||
# )
|
||||
# if not hasattr(baseprefix.elt, "information"):
|
||||
# baseprefix.elt.information = self.objectspace.information(
|
||||
# baseprefix.elt.xmlfiles
|
||||
# )
|
||||
# for key, value in self.objectspace.paths.get_providers_path(
|
||||
# path_prefix
|
||||
# ).items():
|
||||
# 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.objectspace.base_option_name)
|
||||
baseelt.description = self.objectspace.base_option_name
|
||||
self.reflector_objects[baseelt.path].get(
|
||||
[], baseelt.description
|
||||
) # pylint: disable=E1101
|
||||
|
|
@ -242,16 +166,12 @@ class TiramisuReflector:
|
|||
self.objectspace.set_name(elt, "optiondescription_")
|
||||
return self.objectspace.reflector_names[elt.path]
|
||||
|
||||
def get_information_name(self):
|
||||
self.informations_idx += 1
|
||||
return f"information_{self.informations_idx}"
|
||||
|
||||
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"])
|
||||
|
||||
|
||||
|
|
@ -269,19 +189,24 @@ class Common:
|
|||
self.tiramisu = tiramisu
|
||||
tiramisu.reflector_objects[elt.path] = self
|
||||
self.object_type = None
|
||||
self.informations = []
|
||||
|
||||
def get(self, calls, parent_name):
|
||||
"""Get tiramisu's object"""
|
||||
self_calls = calls.copy()
|
||||
if self.elt.path in self_calls:
|
||||
if self.elt.path in calls:
|
||||
msg = f'"{self.elt.path}" will make an infinite loop'
|
||||
raise DictConsistencyError(msg, 80, self.elt.xmlfiles)
|
||||
self_calls = calls.copy()
|
||||
self_calls.append(self.elt.path)
|
||||
self.calls = self_calls
|
||||
if self.option_name is None:
|
||||
self.option_name = self.objectspace.reflector_names[self.elt.path]
|
||||
self.populate_attrib()
|
||||
self.populate_informations()
|
||||
if self.informations:
|
||||
for information in self.informations:
|
||||
self.tiramisu.text["option"].append(
|
||||
f"{information}.set_option({self.option_name})"
|
||||
)
|
||||
return self.option_name
|
||||
|
||||
def populate_attrib(self):
|
||||
|
|
@ -294,6 +219,7 @@ class Common:
|
|||
keys["properties"] = self.properties_to_string(
|
||||
self.objectspace.properties[self.elt.path]
|
||||
)
|
||||
self.populate_informations(keys)
|
||||
attrib = ", ".join([f"{key}={value}" for key, value in keys.items()])
|
||||
self.tiramisu.text["option"].append(
|
||||
f"{self.option_name} = {self.object_type}({attrib})"
|
||||
|
|
@ -322,12 +248,11 @@ class Common:
|
|||
for property_, value in values.items():
|
||||
if value is True:
|
||||
properties.append(self.convert_str(property_))
|
||||
elif isinstance(value, list):
|
||||
for val in value:
|
||||
calc_properties.append(self.calculation_value(val))
|
||||
else:
|
||||
if isinstance(value, list):
|
||||
for val in value:
|
||||
calc_properties.append(self.calculation_value(val))
|
||||
else:
|
||||
calc_properties.append(self.calculation_value(value))
|
||||
calc_properties.append(self.calculation_value(value))
|
||||
return "frozenset({" + ", ".join(sorted(properties) + calc_properties) + "})"
|
||||
|
||||
def calc_properties(
|
||||
|
|
@ -350,17 +275,12 @@ class Common:
|
|||
f"kwargs={{{kwargs}}}), func['calc_value_property_help'])"
|
||||
)
|
||||
|
||||
def populate_informations(self):
|
||||
def populate_informations(self, keys):
|
||||
"""Populate Tiramisu's informations"""
|
||||
informations = self.objectspace.informations.get(self.elt.path)
|
||||
if not informations:
|
||||
return
|
||||
for key, value in informations.items():
|
||||
if isinstance(value, str):
|
||||
value = self.convert_str(value)
|
||||
self.tiramisu.text["option"].append(
|
||||
f"{self.option_name}.impl_set_information('{key}', {value})"
|
||||
)
|
||||
keys["informations"] = informations
|
||||
|
||||
def populate_param(
|
||||
self,
|
||||
|
|
@ -373,7 +293,10 @@ class Common:
|
|||
else:
|
||||
value = param
|
||||
return f"ParamValue({value})"
|
||||
if param["type"] == "value":
|
||||
return f"ParamValue({param['value']})"
|
||||
if param["type"] == "information":
|
||||
# default? really?
|
||||
if self.elt.multi:
|
||||
default = []
|
||||
else:
|
||||
|
|
@ -381,9 +304,27 @@ class Common:
|
|||
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)})'
|
||||
information_variable_path = param["variable"].path
|
||||
information_variable = self.tiramisu.reflector_objects[
|
||||
information_variable_path
|
||||
]
|
||||
if information_variable_path not in self.calls:
|
||||
option_name = information_variable.get(self.calls, self.elt.path)
|
||||
return f'ParamInformation("{param["information"]}", {default}, option={option_name})'
|
||||
else:
|
||||
information = (
|
||||
f'ParamInformation("{param["information"]}", {default})'
|
||||
)
|
||||
information_name = self.tiramisu.get_information_name()
|
||||
self.tiramisu.text["option"].append(
|
||||
f"{information_name} = {information}"
|
||||
)
|
||||
information_variable.informations.append(information_name)
|
||||
return information_name
|
||||
return f'ParamInformation("{param["information"]}", {default})'
|
||||
if param["type"] == "suffix":
|
||||
if "suffix" in param and param["suffix"] != None:
|
||||
return f"ParamSuffix(suffix_index={param['suffix']})"
|
||||
return "ParamSuffix()"
|
||||
if param["type"] == "index":
|
||||
return "ParamIndex()"
|
||||
|
|
@ -393,6 +334,7 @@ class Common:
|
|||
param.get("propertyerror", True),
|
||||
param.get("suffix"),
|
||||
param.get("dynamic"),
|
||||
param.get('whole', False),
|
||||
)
|
||||
if param["type"] == "any":
|
||||
if isinstance(param["value"], str):
|
||||
|
|
@ -404,24 +346,25 @@ class Common:
|
|||
|
||||
def build_option_param(
|
||||
self,
|
||||
param,
|
||||
variable,
|
||||
propertyerror,
|
||||
suffix: Optional[str],
|
||||
dynamic,
|
||||
whole: bool,
|
||||
) -> str:
|
||||
"""build variable parameters"""
|
||||
if param.path == self.elt.path:
|
||||
return "ParamSelfOption(whole=False)"
|
||||
option_name = self.tiramisu.reflector_objects[param.path].get(
|
||||
if variable.path == self.elt.path:
|
||||
return f"ParamSelfOption(whole={whole})"
|
||||
if whole:
|
||||
msg = f'variable param "{variable.path}" has whole attribute but it\'s not allowed for external variable'
|
||||
raise DictConsistencyError(msg, 34, self.elt.xmlfiles)
|
||||
option_name = self.tiramisu.reflector_objects[variable.path].get(
|
||||
self.calls, self.elt.path
|
||||
)
|
||||
params = [f"{option_name}"]
|
||||
if suffix is not None:
|
||||
param_type = "ParamDynOption"
|
||||
family = self.tiramisu.reflector_objects[dynamic.path].get(
|
||||
self.calls, self.elt.path
|
||||
)
|
||||
params.extend([f"'{suffix}'", f"{family}"])
|
||||
params.append(self.convert_str(suffix))
|
||||
else:
|
||||
param_type = "ParamOption"
|
||||
if not propertyerror:
|
||||
|
|
@ -433,7 +376,6 @@ class Common:
|
|||
function,
|
||||
) -> str:
|
||||
"""Generate calculated value"""
|
||||
self.tiramisu.add_jinja_support()
|
||||
child = function.to_function(self.objectspace)
|
||||
new_args = []
|
||||
kwargs = []
|
||||
|
|
@ -460,6 +402,41 @@ class Common:
|
|||
ret = ret + ")"
|
||||
return ret
|
||||
|
||||
def populate_calculation(
|
||||
self,
|
||||
datas: Union[Calculation, str, list],
|
||||
return_a_tuple: bool = False,
|
||||
) -> str:
|
||||
if isinstance(datas, str):
|
||||
return self.convert_str(datas)
|
||||
if isinstance(datas, Calculation):
|
||||
return self.calculation_value(datas)
|
||||
if not isinstance(datas, list):
|
||||
return datas
|
||||
params = []
|
||||
for idx, data in enumerate(datas):
|
||||
if isinstance(data, Calculation):
|
||||
try:
|
||||
params.append(self.calculation_value(data))
|
||||
except VariableCalculationDependencyError:
|
||||
pass
|
||||
elif isinstance(data, str):
|
||||
params.append(self.convert_str(data))
|
||||
else:
|
||||
params.append(str(data))
|
||||
if return_a_tuple:
|
||||
ret = "("
|
||||
else:
|
||||
ret = "["
|
||||
ret += ", ".join(params)
|
||||
if return_a_tuple:
|
||||
if len(params) <= 1:
|
||||
ret += ","
|
||||
ret += ")"
|
||||
else:
|
||||
ret += "]"
|
||||
return ret
|
||||
|
||||
|
||||
class Variable(Common):
|
||||
"""Manage variable"""
|
||||
|
|
@ -470,8 +447,8 @@ class Variable(Common):
|
|||
tiramisu,
|
||||
):
|
||||
super().__init__(elt, tiramisu)
|
||||
if elt.type in self.tiramisu.objectspace.rougailconfig['custom_types']:
|
||||
self.object_type = self.tiramisu.objectspace.rougailconfig['custom_types'][elt.type].__name__
|
||||
if elt.type in self.tiramisu.objectspace.custom_types:
|
||||
self.object_type = self.tiramisu.objectspace.custom_types[elt.type].__name__
|
||||
else:
|
||||
self.object_type = CONVERT_OPTION[elt.type]["opttype"]
|
||||
|
||||
|
|
@ -483,58 +460,39 @@ class Variable(Common):
|
|||
keys["opt"] = self.tiramisu.reflector_objects[self.elt.opt.path].get(
|
||||
self.calls, self.elt.path
|
||||
)
|
||||
return
|
||||
if self.elt.type == "choice":
|
||||
choices = self.elt.choices
|
||||
if isinstance(choices, Calculation):
|
||||
keys["values"] = self.calculation_value(choices)
|
||||
else:
|
||||
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"] += ")"
|
||||
keys["values"] = self.populate_calculation(
|
||||
self.elt.choices, return_a_tuple=True
|
||||
)
|
||||
if self.elt.type == 'regexp':
|
||||
self.object_type = 'Regexp_' + self.option_name
|
||||
self.tiramisu.text['header'].append(f'''class {self.object_type}(RegexpOption):
|
||||
__slots__ = tuple()
|
||||
_type = 'value'
|
||||
{self.object_type}._regexp = re_compile(r"{self.elt.regexp}")
|
||||
''')
|
||||
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):
|
||||
value = value.copy()
|
||||
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
|
||||
try:
|
||||
keys["default"] = self.populate_calculation(self.elt.default)
|
||||
except VariableCalculationDependencyError:
|
||||
pass
|
||||
if self.elt.path in self.objectspace.default_multi:
|
||||
value = self.objectspace.default_multi[self.elt.path]
|
||||
try:
|
||||
keys["default_multi"] = self.populate_calculation(
|
||||
self.objectspace.default_multi[self.elt.path]
|
||||
)
|
||||
except VariableCalculationDependencyError:
|
||||
pass
|
||||
if self.elt.validators:
|
||||
keys["validators"] = self.populate_calculation(self.elt.validators)
|
||||
for key, value in (
|
||||
CONVERT_OPTION.get(self.elt.type, {}).get("initkwargs", {}).items()
|
||||
):
|
||||
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) + "]"
|
||||
for key, value in CONVERT_OPTION.get(self.elt.type, {}).get("initkwargs", {}).items():
|
||||
if isinstance(value, str):
|
||||
value = f"'{value}'"
|
||||
keys[key] = value
|
||||
if self.elt.params:
|
||||
for param in self.elt.params:
|
||||
|
|
@ -571,12 +529,7 @@ class Family(Common):
|
|||
keys: list,
|
||||
) -> None:
|
||||
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))))"
|
||||
keys["suffixes"] = self.populate_calculation(self.elt.dynamic)
|
||||
children = []
|
||||
for path in self.objectspace.parents[self.elt.path]:
|
||||
children.append(self.objectspace.paths[path])
|
||||
|
|
|
|||
21
src/rougail/update/__init__.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
"""
|
||||
Silique (https://www.silique.fr)
|
||||
Copyright (C) 2024
|
||||
|
||||
distribued with GPL-2 or later license
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
"""
|
||||
from .update import RougailUpgrade
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
"""Update Rougail XML file to new version
|
||||
"""Update Rougail structure file to new version
|
||||
|
||||
Cadoles (http://www.cadoles.com)
|
||||
Copyright (C) 2021
|
||||
|
|
@ -23,38 +23,28 @@ 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, Any, Optional, Tuple
|
||||
from os.path import join, isfile, isdir, basename
|
||||
from os import listdir, makedirs
|
||||
from os import listdir
|
||||
from os.path import basename, isdir, isfile, join
|
||||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
try:
|
||||
from lxml.etree import parse, XMLParser, XMLSyntaxError # pylint: disable=E0611
|
||||
from lxml.etree import Element, SubElement, tostring
|
||||
from lxml.etree import SubElement # pylint: disable=E0611
|
||||
from lxml.etree import Element, XMLParser, XMLSyntaxError, parse, tostring
|
||||
except ModuleNotFoundError as err:
|
||||
parse = None
|
||||
|
||||
# from ast import parse as ast_parse
|
||||
from json import dumps
|
||||
from yaml import safe_load, dump, SafeDumper
|
||||
from ruamel.yaml import YAML
|
||||
from pathlib import Path
|
||||
|
||||
from .i18n import _
|
||||
from .error import UpgradeError
|
||||
from ..config import RougailConfig
|
||||
from ..error import UpgradeError
|
||||
from ..i18n import _
|
||||
from ..object_model import CONVERT_OPTION
|
||||
from ..utils import normalize_family
|
||||
|
||||
from .utils import normalize_family
|
||||
from .config import RougailConfig
|
||||
from .object_model import CONVERT_OPTION
|
||||
|
||||
|
||||
VERSIONS = ["0.10", "1.0"]
|
||||
|
||||
FIXME_PRINT_FILENAME = True
|
||||
FIXME_PRINT_FILENAME = False
|
||||
FIXME_PRINT_FILE = True
|
||||
FIXME_PRINT_FILE = False
|
||||
FIXME_PRINT_UNKNOWN_VAR = True
|
||||
FIXME_PRINT_UNKNOWN_VAR = False
|
||||
FIXME_PRINT_REMOVE = True
|
||||
FIXME_PRINT_REMOVE = False
|
||||
VERSIONS = ["0.10", "1.0", "1.1"]
|
||||
|
||||
|
||||
def get_function_name(version):
|
||||
|
|
@ -65,24 +55,15 @@ def get_function_name(version):
|
|||
FUNCTION_VERSIONS = [(version, get_function_name(version)) for version in VERSIONS]
|
||||
|
||||
|
||||
class NoAliasDumper(SafeDumper):
|
||||
def ignore_aliases(self, data):
|
||||
return True
|
||||
|
||||
|
||||
class upgrade_010_to_100:
|
||||
class upgrade_010_to_10:
|
||||
def __init__(
|
||||
self,
|
||||
dico: dict,
|
||||
namespace: str,
|
||||
xmlsrc: str,
|
||||
) -> None:
|
||||
if FIXME_PRINT_FILE:
|
||||
from pprint import pprint
|
||||
|
||||
pprint(dico)
|
||||
self.xmlsrc = xmlsrc
|
||||
self.paths = {"family": {}, "variable": {}}
|
||||
self.paths = {"family": {}, "variable": {}, 'dynamic': {}}
|
||||
self.lists = {
|
||||
"service": {},
|
||||
"ip": {},
|
||||
|
|
@ -90,26 +71,29 @@ class upgrade_010_to_100:
|
|||
"file": {},
|
||||
}
|
||||
self.flatten_paths = {"family": {}, "variable": {}}
|
||||
self.variables = self.parse_variables(dico, namespace)
|
||||
self.variables = self.parse_variables(dico, namespace, namespace, root=True)
|
||||
self.parse_variables_with_path()
|
||||
self.parse_services(dico)
|
||||
self.parse_constraints(dico)
|
||||
if FIXME_PRINT_FILE:
|
||||
print("==")
|
||||
pprint(self.variables)
|
||||
pprint(self.services)
|
||||
|
||||
def parse_variables(
|
||||
self,
|
||||
family: dict,
|
||||
sub_path: str,
|
||||
true_sub_path: str,
|
||||
*,
|
||||
root: bool=False,
|
||||
is_dynamic: bool=False,
|
||||
) -> dict:
|
||||
new_families = {}
|
||||
if root:
|
||||
new_families['version'] = None
|
||||
if "variables" in family:
|
||||
for subelt in family["variables"]:
|
||||
for typ, obj in subelt.items():
|
||||
for subobj in obj:
|
||||
getattr(self, f"convert_{typ}")(subobj, new_families, sub_path)
|
||||
local_is_dynamic = is_dynamic or subobj.get('dynamic') is not None
|
||||
getattr(self, f"convert_{typ}")(subobj, new_families, sub_path, true_sub_path, local_is_dynamic)
|
||||
family.pop("variables")
|
||||
return new_families
|
||||
|
||||
|
|
@ -118,9 +102,17 @@ class upgrade_010_to_100:
|
|||
family: dict,
|
||||
new_families: dict,
|
||||
sub_path: str,
|
||||
true_sub_path: str,
|
||||
is_dynamic: bool,
|
||||
) -> None:
|
||||
# name is the key, do not let it in values
|
||||
name = family.pop("name")
|
||||
if true_sub_path:
|
||||
true_sub_path = true_sub_path + "." + name
|
||||
else:
|
||||
true_sub_path = name
|
||||
if is_dynamic:
|
||||
name += '{{ suffix }}'
|
||||
if sub_path:
|
||||
sub_path = sub_path + "." + name
|
||||
else:
|
||||
|
|
@ -134,7 +126,7 @@ class upgrade_010_to_100:
|
|||
if typ == "dynamic":
|
||||
family["variable"] = self.get_variable_path(value)
|
||||
# add sub families and sub variables
|
||||
sub_families = self.parse_variables(family, sub_path)
|
||||
sub_families = self.parse_variables(family, sub_path, true_sub_path, is_dynamic=is_dynamic)
|
||||
for sub_name, sub_family in sub_families.copy().items():
|
||||
if sub_name not in family:
|
||||
continue
|
||||
|
|
@ -150,22 +142,34 @@ class upgrade_010_to_100:
|
|||
variable: dict,
|
||||
new_families: dict,
|
||||
sub_path: str,
|
||||
true_sub_path: str,
|
||||
is_dynamic: bool,
|
||||
) -> dict:
|
||||
name = variable.pop("name")
|
||||
if is_dynamic:
|
||||
if true_sub_path:
|
||||
true_sub_path = true_sub_path + "." + name
|
||||
else:
|
||||
true_sub_path = name
|
||||
self.flatten_paths["variable"][name] = true_sub_path
|
||||
name += '{{ suffix }}'
|
||||
if sub_path:
|
||||
sub_path = sub_path + "." + name
|
||||
else:
|
||||
sub_path = name
|
||||
if is_dynamic:
|
||||
self.paths['dynamic'][true_sub_path] = sub_path
|
||||
new_families[name] = variable
|
||||
self.flatten_paths["variable"][name] = sub_path
|
||||
self.paths["variable"][sub_path] = variable
|
||||
if "redefine" not in variable and "value" not in variable and "mandatory" not in variable and ("type" not in variable or variable["type"] != "boolean"):
|
||||
if (
|
||||
"redefine" not in variable
|
||||
and "value" not in variable
|
||||
and "mandatory" not in variable
|
||||
and ("type" not in variable or variable["type"] != "boolean")
|
||||
):
|
||||
variable["mandatory"] = False
|
||||
if "remove_condition" in variable and variable.pop("remove_condition"):
|
||||
if FIXME_PRINT_REMOVE:
|
||||
print(
|
||||
f"variable {name} in file {self.xmlsrc} has remove_condition, all properties (hidden, disabled and mandatory) are set to False"
|
||||
)
|
||||
for prop in ["hidden", "disabled", "mandatory"]:
|
||||
if prop not in variable:
|
||||
variable[prop] = False
|
||||
|
|
@ -194,24 +198,32 @@ class upgrade_010_to_100:
|
|||
)(test)
|
||||
)
|
||||
variable["test"] = tests
|
||||
if variable.get('mandatory', False):
|
||||
del variable["mandatory"]
|
||||
CONVERT_TYPE = {'filename': 'unix_filename',
|
||||
'password': 'secret',
|
||||
}
|
||||
if variable.get('type') in CONVERT_TYPE:
|
||||
variable['type'] = CONVERT_TYPE[variable['type']]
|
||||
|
||||
def parse_variables_with_path(self):
|
||||
for variable in self.paths["variable"].values():
|
||||
multi = variable.get('multi', False)
|
||||
if "value" in variable:
|
||||
default = variable.pop("value")
|
||||
if default is not None:
|
||||
if not variable.get("multi", False) and len(default) == 1:
|
||||
variable["default"] = self.get_value(default[0])
|
||||
if not multi and len(default) == 1:
|
||||
variable["default"] = self.get_value(default[0], multi)
|
||||
else:
|
||||
variable["default"] = [
|
||||
self.get_value(value) for value in default
|
||||
self.get_value(value, multi) for value in default
|
||||
]
|
||||
if "choice" in variable:
|
||||
if not variable["choice"]:
|
||||
variable["choices"] = variable.pop("choice")
|
||||
else:
|
||||
variable["choices"] = [
|
||||
self.get_value(choice) for choice in variable.pop("choice")
|
||||
self.get_value(choice, multi) for choice in variable.pop("choice")
|
||||
]
|
||||
|
||||
def parse_services(
|
||||
|
|
@ -327,34 +339,43 @@ class upgrade_010_to_100:
|
|||
apply_on_fallback = False
|
||||
source = self.get_variable_path(condition["source"])
|
||||
if not source:
|
||||
source = f'__{condition["source"]}'
|
||||
source = condition["source"]
|
||||
name = condition.pop("name")
|
||||
prop = name.split("_", 1)[0]
|
||||
if apply_on_fallback:
|
||||
condition_value = True
|
||||
else:
|
||||
condition_value = self.params_condition_to_jinja(
|
||||
source, condition["param"], name.endswith("if_in")
|
||||
)
|
||||
multi = False
|
||||
for target in condition["target"]:
|
||||
typ = target.get("type", "variable")
|
||||
if typ == "variable":
|
||||
variable_path = self.get_variable_path(target["text"])
|
||||
if variable_path is None:
|
||||
continue
|
||||
variable = self.paths["variable"][variable_path]
|
||||
if variable.get('multi', False):
|
||||
multi = True
|
||||
if apply_on_fallback:
|
||||
condition_value = True
|
||||
else:
|
||||
if "{{ suffix }}" in source:
|
||||
force_param = {'__var': source}
|
||||
source = '__var'
|
||||
else:
|
||||
force_param = None
|
||||
condition_value = self.params_condition_to_jinja(
|
||||
prop, source, condition["param"], name.endswith("if_in"), multi
|
||||
)
|
||||
if force_param:
|
||||
condition_value.setdefault('params', {}).update(force_param)
|
||||
for target in condition["target"]:
|
||||
typ = target.get("type", "variable")
|
||||
if typ == "variable":
|
||||
variable_path = self.get_variable_path(target["text"])
|
||||
if variable_path is None:
|
||||
if FIXME_PRINT_UNKNOWN_VAR and not target.get("optional", False):
|
||||
print(
|
||||
f'pffff la target {target["text"]} de la condition n\'est pas trouvable'
|
||||
)
|
||||
continue
|
||||
variable = self.paths["variable"][variable_path]
|
||||
variable[prop] = condition_value
|
||||
elif typ == "family":
|
||||
family_path = self.get_family_path(target["text"])
|
||||
if family_path is None:
|
||||
if FIXME_PRINT_UNKNOWN_VAR and not target.get("optional", False):
|
||||
print(
|
||||
f'pffff la target {target["text"]} de la condition n\'est pas trouvable'
|
||||
)
|
||||
continue
|
||||
family = self.paths["family"][family_path]
|
||||
family[prop] = condition_value
|
||||
|
|
@ -386,10 +407,6 @@ class upgrade_010_to_100:
|
|||
for target in check["target"]:
|
||||
variable_path = self.get_variable_path(target["text"])
|
||||
if variable_path is None:
|
||||
if FIXME_PRINT_UNKNOWN_VAR and not target.get("optional", False):
|
||||
print(
|
||||
f'pffff la target {target["text"]} dans le check n\'est pas trouvable'
|
||||
)
|
||||
continue
|
||||
variable = self.paths["variable"][variable_path]
|
||||
if "validators" in variable and variable["validators"] is None:
|
||||
|
|
@ -400,7 +417,7 @@ class upgrade_010_to_100:
|
|||
check["param"] = [
|
||||
{"text": variable_path, "type": "variable"}
|
||||
] + check.get("param", [])
|
||||
check_value = self.convert_param_function(check)
|
||||
check_value = self.convert_param_function(check, variable.get('multi', False))
|
||||
variable.setdefault("validators", []).append(check_value)
|
||||
|
||||
def parse_fill(
|
||||
|
|
@ -410,27 +427,30 @@ class upgrade_010_to_100:
|
|||
for target in fill.pop("target"):
|
||||
params = []
|
||||
variable_path = self.get_variable_path(target["text"])
|
||||
if variable_path is None:
|
||||
if FIXME_PRINT_UNKNOWN_VAR and not target.get("optional", False):
|
||||
print(
|
||||
f'pffff la target {target["text"]} dans le fill n\'est pas trouvable'
|
||||
)
|
||||
continue
|
||||
variable = self.paths["variable"][variable_path]
|
||||
if fill.get("type") == "jinja":
|
||||
fill_value = {
|
||||
"type": "jinja",
|
||||
"jinja": fill["name"],
|
||||
}
|
||||
if variable_path in self.paths["dynamic"]:
|
||||
variable_path = self.paths["dynamic"][variable_path]
|
||||
if variable_path in self.paths["variable"]:
|
||||
variable = self.paths["variable"][variable_path]
|
||||
if fill.get("type") == "jinja":
|
||||
fill_value = {
|
||||
"type": "jinja",
|
||||
"jinja": fill["name"],
|
||||
}
|
||||
else:
|
||||
fill_value = self.convert_param_function(fill, variable.get('multi', False))
|
||||
variable["default"] = fill_value
|
||||
if variable.get('mandatory') is False:
|
||||
del variable['mandatory']
|
||||
else:
|
||||
fill_value = self.convert_param_function(fill)
|
||||
variable["default"] = fill_value
|
||||
raise Exception(f'cannot set fill to unknown variable "{variable_path}"')
|
||||
|
||||
def params_condition_to_jinja(
|
||||
self,
|
||||
prop: str,
|
||||
path: str,
|
||||
params: List[dict],
|
||||
if_in: bool,
|
||||
multi: bool,
|
||||
) -> str:
|
||||
new_params = {}
|
||||
jinja = "{% if "
|
||||
|
|
@ -438,15 +458,15 @@ class upgrade_010_to_100:
|
|||
if idx:
|
||||
jinja += " or "
|
||||
|
||||
new_param, value = self.get_jinja_param_and_value(param)
|
||||
new_param, value = self.get_jinja_param_and_value(param, multi)
|
||||
if value:
|
||||
jinja += path + " == " + value
|
||||
if new_param:
|
||||
new_params |= new_param
|
||||
if if_in:
|
||||
jinja += " %}true{% else %}false{% endif %}"
|
||||
jinja += " %}" + prop + "{% endif %}"
|
||||
else:
|
||||
jinja += " %}false{% else %}true{% endif %}"
|
||||
jinja += " %}{% else %}" + prop + "{% endif %}"
|
||||
ret = {
|
||||
"type": "jinja",
|
||||
"jinja": jinja,
|
||||
|
|
@ -458,12 +478,11 @@ class upgrade_010_to_100:
|
|||
def get_value(
|
||||
self,
|
||||
param: dict,
|
||||
multi: bool,
|
||||
) -> Any:
|
||||
# <!ATTLIST type (string|number|nil|space|boolean|variable|function|information|suffix|index) "string">
|
||||
typ = param.get("type", "string")
|
||||
if typ == "string":
|
||||
value = param["text"]
|
||||
# value = dumps(value, ensure_ascii=False)
|
||||
value = param.get("text")
|
||||
elif typ == "number":
|
||||
value = int(param["text"])
|
||||
elif typ == "nil":
|
||||
|
|
@ -485,7 +504,7 @@ class upgrade_010_to_100:
|
|||
if "propertyerror" in param:
|
||||
value["propertyerror"] = param["propertyerror"]
|
||||
elif typ == "function":
|
||||
value = self.convert_param_function(param)
|
||||
value = self.convert_param_function(param, multi)
|
||||
elif typ == "information":
|
||||
value = {
|
||||
"type": "information",
|
||||
|
|
@ -503,10 +522,11 @@ class upgrade_010_to_100:
|
|||
def get_jinja_param_and_value(
|
||||
self,
|
||||
param,
|
||||
multi: bool,
|
||||
) -> Tuple[list, Any]:
|
||||
new_param = None
|
||||
typ = param.get("type", "string")
|
||||
value = self.get_value(param)
|
||||
value = self.get_value(param, multi)
|
||||
if isinstance(value, dict):
|
||||
if typ == "information":
|
||||
key = normalize_family(value["information"])
|
||||
|
|
@ -514,17 +534,25 @@ class upgrade_010_to_100:
|
|||
attr_name = f'{value["variable"]}.{key}'
|
||||
else:
|
||||
attr_name = key
|
||||
attr_name = f"__information.{attr_name}"
|
||||
attr_name = f"__information_{attr_name}"
|
||||
new_param = {attr_name: value}
|
||||
value = attr_name
|
||||
elif typ in ["index", "suffix"]:
|
||||
attr_name = f"__{typ}"
|
||||
new_param = {attr_name: value}
|
||||
if 'name' in value:
|
||||
attr_name = value['name']
|
||||
else:
|
||||
attr_name = f"__{typ}"
|
||||
new_param = {attr_name: {"type": typ}}
|
||||
value = attr_name
|
||||
elif "propertyerror" in param or "optional" in param:
|
||||
attr_name = value["variable"]
|
||||
new_param = {attr_name: value}
|
||||
value = value[typ]
|
||||
elif "{{ suffix }}" in value[typ]:
|
||||
path = value[typ]
|
||||
attr_name = path.split('.')[-1][:-12] # remove {{ suffix }}
|
||||
new_param = {attr_name: value}
|
||||
value = attr_name
|
||||
else:
|
||||
value = value[typ]
|
||||
if not value:
|
||||
|
|
@ -536,23 +564,35 @@ class upgrade_010_to_100:
|
|||
def convert_param_function(
|
||||
self,
|
||||
param: dict,
|
||||
multi: bool,
|
||||
) -> str:
|
||||
text = param["name"]
|
||||
params = {}
|
||||
# multi = False
|
||||
if "param" in param and param["param"]:
|
||||
if text == 'calc_value' and len(param["param"]) == 1 and isinstance(param["param"][0], dict) and param["param"][0].get('type') == 'variable' and param["param"][0].get("text"):
|
||||
value = param["param"][0]["text"]
|
||||
path = self.get_variable_path(value)
|
||||
if not path:
|
||||
path = value
|
||||
ret = {"type": "variable", "variable": path}
|
||||
if 'optional' in param["param"][0]:
|
||||
ret['optional'] = param["param"][0]["optional"]
|
||||
return ret
|
||||
first, *others = param["param"]
|
||||
new_param, first = self.get_jinja_param_and_value(first)
|
||||
new_param, first = self.get_jinja_param_and_value(first, multi)
|
||||
text = f"{first} | {text}"
|
||||
if new_param:
|
||||
params |= new_param
|
||||
if others:
|
||||
values = []
|
||||
for param in others:
|
||||
new_param, value = self.get_jinja_param_and_value(param)
|
||||
new_param, value = self.get_jinja_param_and_value(param, multi)
|
||||
if new_param:
|
||||
params |= new_param
|
||||
# if param.get('type') != 'variable' or value is not None:
|
||||
if "name" in param:
|
||||
if param["name"] == "multi" and value == "true":
|
||||
multi = True
|
||||
values.append(f'{param["name"]}={value}')
|
||||
else:
|
||||
values.append(value)
|
||||
|
|
@ -561,7 +601,12 @@ class upgrade_010_to_100:
|
|||
text += ")"
|
||||
else:
|
||||
text += "()"
|
||||
text = "{{ " + text + " }}"
|
||||
if not multi:
|
||||
text = "{{ " + text + " }}"
|
||||
else:
|
||||
text = """{% for __variable in """ + text + """ %}
|
||||
{{ __variable }}
|
||||
{% endfor %}"""
|
||||
ret = {"type": "jinja", "jinja": text}
|
||||
if params:
|
||||
ret["params"] = params
|
||||
|
|
@ -576,10 +621,10 @@ class upgrade_010_to_100:
|
|||
and path in self.flatten_paths["variable"]
|
||||
):
|
||||
path = self.flatten_paths["variable"][path]
|
||||
if path in self.paths["dynamic"]:
|
||||
path = self.paths["dynamic"][path]
|
||||
if path not in self.paths["variable"]:
|
||||
if FIXME_PRINT_UNKNOWN_VAR:
|
||||
print("pffff impossible de trouver la variable", path)
|
||||
return
|
||||
return path
|
||||
return path
|
||||
|
||||
def get_family_path(
|
||||
|
|
@ -589,8 +634,6 @@ class upgrade_010_to_100:
|
|||
if path not in self.paths["family"] and path in self.flatten_paths["family"]:
|
||||
path = self.flatten_paths["family"][path]
|
||||
if path not in self.paths["family"]:
|
||||
if FIXME_PRINT_UNKNOWN_VAR:
|
||||
print("pffff impossible de trouver la famille", path)
|
||||
return
|
||||
return path
|
||||
|
||||
|
|
@ -613,42 +656,38 @@ class RougailUpgrade:
|
|||
rougailconfig = RougailConfig
|
||||
self.rougailconfig = rougailconfig
|
||||
|
||||
def load_dictionaries(
|
||||
def run(
|
||||
self,
|
||||
# srcfolder: str,
|
||||
dstfolder: str,
|
||||
services_dstfolder: Optional[str],
|
||||
extra_dstfolder: Optional[str] = None,
|
||||
# namespace: str,
|
||||
# display: bool=True,
|
||||
):
|
||||
if extra_dstfolder is None:
|
||||
extra_dstfolder = dstfolder
|
||||
self._load_dictionaries(
|
||||
self.rougailconfig["dictionaries_dir"],
|
||||
dstfolder,
|
||||
services_dstfolder,
|
||||
self.rougailconfig["variable_namespace"],
|
||||
)
|
||||
for namespace, extra_dirs in self.rougailconfig["extra_dictionaries"].items():
|
||||
extra_dstsubfolder = join(extra_dstfolder, namespace)
|
||||
if not isdir(extra_dstsubfolder):
|
||||
makedirs(extra_dstsubfolder)
|
||||
for extra_dir in extra_dirs:
|
||||
self._load_dictionaries(
|
||||
extra_dir,
|
||||
extra_dstsubfolder,
|
||||
None,
|
||||
namespace,
|
||||
)
|
||||
for dict_dir, dest_dir in zip(self.rougailconfig["main_dictionaries"], self.rougailconfig["upgrade_options.main_dictionaries"]):
|
||||
self._load_dictionaries(
|
||||
dict_dir,
|
||||
dest_dir,
|
||||
normalize_family(self.rougailconfig["main_namespace"]),
|
||||
)
|
||||
if self.rougailconfig['main_namespace']:
|
||||
if self.rougailconfig["extra_dictionaries"]:
|
||||
dst_extra_dir = self.rougailconfig["upgrade_options.extra_dictionary"]
|
||||
for namespace, extra_dirs in self.rougailconfig["extra_dictionaries"].items():
|
||||
extra_dstsubfolder = Path(dst_extra_dir) / namespace
|
||||
if not extra_dstsubfolder.is_dir():
|
||||
extra_dstsubfolder.mkdir()
|
||||
for extra_dir in extra_dirs:
|
||||
self._load_dictionaries(
|
||||
str(extra_dir),
|
||||
str(extra_dstsubfolder),
|
||||
normalize_family(namespace),
|
||||
)
|
||||
|
||||
def _load_dictionaries(
|
||||
self,
|
||||
srcfolder: str,
|
||||
dstfolder: str,
|
||||
services_dstfolder: Optional[str],
|
||||
dstfolder: Optional[str],
|
||||
namespace: str,
|
||||
) -> None:
|
||||
if dstfolder is None:
|
||||
dstfolder = srcfolder
|
||||
Path(dstfolder).mkdir(parents=True, exist_ok=True)
|
||||
filenames = [
|
||||
filename
|
||||
for filename in listdir(srcfolder)
|
||||
|
|
@ -657,21 +696,11 @@ class RougailUpgrade:
|
|||
filenames.sort()
|
||||
for filename in filenames:
|
||||
xmlsrc = Path(srcfolder) / Path(filename)
|
||||
ymlfile = filename[:-3] + "yml"
|
||||
xmldst = Path(dstfolder) / Path(ymlfile)
|
||||
if xmldst.is_file():
|
||||
raise Exception(
|
||||
f'cannot update "{xmlsrc}" destination file "{xmldst}" already exists'
|
||||
)
|
||||
if services_dstfolder:
|
||||
ymldst_services = Path(services_dstfolder) / ymlfile
|
||||
if ymldst_services.is_file():
|
||||
raise Exception(
|
||||
f'cannot update "{xmlsrc}" destination file "{ymldst_services}" already exists'
|
||||
)
|
||||
|
||||
ymldst = Path(dstfolder) / (Path(filename).stem + '.yml')
|
||||
if filename.endswith(".xml"):
|
||||
if parse is None:
|
||||
raise Exception('XML module is not installed')
|
||||
raise Exception("XML module is not installed")
|
||||
try:
|
||||
parser = XMLParser(remove_blank_text=True)
|
||||
document = parse(xmlsrc, parser)
|
||||
|
|
@ -683,61 +712,43 @@ class RougailUpgrade:
|
|||
)
|
||||
ext = "xml"
|
||||
else:
|
||||
with xmlsrc.open() as xml_fh:
|
||||
root = safe_load(xml_fh)
|
||||
search_function_name = get_function_name(root["version"])
|
||||
with xmlsrc.open() as file_fh:
|
||||
root = YAML(typ="safe").load(file_fh)
|
||||
search_function_name = get_function_name(str(root["version"]))
|
||||
ext = "yml"
|
||||
function_found = False
|
||||
if FIXME_PRINT_FILENAME:
|
||||
print(
|
||||
"========================================================================"
|
||||
)
|
||||
print(xmlsrc)
|
||||
print(
|
||||
"========================================================================"
|
||||
)
|
||||
root_services = None
|
||||
for version, function_version in FUNCTION_VERSIONS:
|
||||
if function_found and hasattr(self, function_version):
|
||||
# if display:
|
||||
# print(f' - convert {filename} to version {version}')
|
||||
upgrade_help = self.upgrade_help.get(function_version, {}).get(
|
||||
filename, {}
|
||||
)
|
||||
if upgrade_help.get("remove") is True:
|
||||
continue
|
||||
root, root_services, new_type = getattr(self, function_version)(
|
||||
root, root_services_, new_type = getattr(self, function_version)(
|
||||
root, upgrade_help, namespace, xmlsrc, ext
|
||||
)
|
||||
if root_services_ is not None:
|
||||
root_services = root_services_
|
||||
if function_version == search_function_name:
|
||||
function_found = True
|
||||
if root:
|
||||
root["version"] = version
|
||||
xmldst.parent.mkdir(parents=True, exist_ok=True)
|
||||
with xmldst.open("w") as ymlfh:
|
||||
dump(
|
||||
if root != {'version': None}:
|
||||
root["version"] = float(version)
|
||||
with ymldst.open("w") as ymlfh:
|
||||
yaml = YAML()
|
||||
yaml.dump(
|
||||
root,
|
||||
ymlfh,
|
||||
allow_unicode=True,
|
||||
sort_keys=False,
|
||||
Dumper=NoAliasDumper,
|
||||
)
|
||||
if root_services and services_dstfolder:
|
||||
root_services["version"] = version
|
||||
ymldst_services.parent.mkdir(parents=True, exist_ok=True)
|
||||
with ymldst_services.open("w") as ymlfh:
|
||||
dump(
|
||||
root_services,
|
||||
ymlfh,
|
||||
allow_unicode=True,
|
||||
sort_keys=False,
|
||||
Dumper=NoAliasDumper,
|
||||
)
|
||||
|
||||
# if not self.dtd.validate(document):
|
||||
# dtd_error = self.dtd.error_log.filter_from_errors()[0]
|
||||
# msg = _(f'not a valid XML file: {dtd_error}')
|
||||
# raise DictConsistencyError(msg, 43, [xmlfile])
|
||||
# yield xmlfile, document.getroot()
|
||||
# if root_services and services_dstfolder:
|
||||
# root_services["version"] = version
|
||||
# ymldst_services.parent.mkdir(parents=True, exist_ok=True)
|
||||
# with ymldst_services.open("w") as ymlfh:
|
||||
# yaml = YAML()
|
||||
# yaml.dump(
|
||||
# root_services,
|
||||
# ymlfh,
|
||||
# )
|
||||
|
||||
def _attribut_to_bool(self, variable):
|
||||
for prop in [
|
||||
|
|
@ -766,10 +777,11 @@ class RougailUpgrade:
|
|||
def _attribut_to_int(self, variable):
|
||||
for prop in ["mode"]:
|
||||
if prop in variable:
|
||||
if variable[prop] in ['expert', 'normal']:
|
||||
variable[prop] = {'expert': 'advanced',
|
||||
'normal': 'standard',
|
||||
}.get(variable[prop])
|
||||
if variable[prop] in ["expert", "normal"]:
|
||||
variable[prop] = {
|
||||
"expert": "advanced",
|
||||
"normal": "standard",
|
||||
}.get(variable[prop])
|
||||
continue
|
||||
try:
|
||||
variable[prop] = int(variable[prop])
|
||||
|
|
@ -891,6 +903,60 @@ class RougailUpgrade:
|
|||
dico = {obj_name: dico}
|
||||
return dico
|
||||
|
||||
def _update_1_1(self, root):
|
||||
new_root = {}
|
||||
update_root = False
|
||||
for key, value in root.items():
|
||||
new_root[key] = value
|
||||
if not isinstance(value, dict):
|
||||
continue
|
||||
# migrate dynamic family
|
||||
if (
|
||||
("variable" in value and isinstance(value["variable"], str))
|
||||
or ("_variable" in value and isinstance(value["_variable"], str))
|
||||
) and (
|
||||
("_type" in value and value["_type"] == "dynamic")
|
||||
or ("type" in value and value["type"] == "dynamic")
|
||||
):
|
||||
value["dynamic"] = {
|
||||
"type": "variable",
|
||||
"variable": value.pop("variable"),
|
||||
"propertyerror": False,
|
||||
}
|
||||
if '{{ suffix }}' not in key:
|
||||
new_root[key + '{{ suffix }}'] = new_root.pop(key)
|
||||
update_root = True
|
||||
self._update_1_1(value)
|
||||
for typ, obj in {'boolean': bool,
|
||||
'number': int,
|
||||
'string': str,
|
||||
'float': float,
|
||||
}.items():
|
||||
if value.get('type') == typ:
|
||||
default = value.get('default')
|
||||
if default is None or default == []:
|
||||
continue
|
||||
if isinstance(default, obj):
|
||||
del value['type']
|
||||
elif isinstance(default, list) and isinstance(default[0], obj):
|
||||
del value['type']
|
||||
if value.get('multi') and isinstance(value.get('default'), list):
|
||||
del value['multi']
|
||||
if update_root:
|
||||
root.clear()
|
||||
root.update(new_root)
|
||||
|
||||
def update_1_1(
|
||||
self,
|
||||
root,
|
||||
upgrade_help: dict,
|
||||
namespace: str,
|
||||
xmlsrc: str,
|
||||
ext: str,
|
||||
):
|
||||
self._update_1_1(root)
|
||||
return root, None, "yml"
|
||||
|
||||
def update_1_0(
|
||||
self,
|
||||
root: "Element",
|
||||
|
|
@ -906,22 +972,12 @@ class RougailUpgrade:
|
|||
objects = root.find(typ)
|
||||
if objects is None:
|
||||
objects = []
|
||||
new_objects = self._xml_to_yaml(objects, typ, variables, "")
|
||||
new_objects = self._xml_to_yaml(objects, typ, variables, namespace)
|
||||
if new_objects[typ]:
|
||||
new_root.update(new_objects)
|
||||
# services = root.find('services')
|
||||
# if services is None:
|
||||
# services = []
|
||||
# new_services = self._xml_to_yaml_service(services)
|
||||
# if new_services:
|
||||
# new_root['services'] = new_services
|
||||
# paths = self._get_path_variables(variables,
|
||||
# namespace == 'configuration',
|
||||
# namespace,
|
||||
# )
|
||||
else:
|
||||
new_root = root
|
||||
variables, services = upgrade_010_to_100(new_root, namespace, xmlsrc).get()
|
||||
variables, services = upgrade_010_to_10(new_root, namespace, xmlsrc).get()
|
||||
return variables, services, "yml"
|
||||
|
||||
def update_0_10(
|
||||
|
|
@ -966,7 +1022,6 @@ class RougailUpgrade:
|
|||
if not has_value:
|
||||
value = SubElement(variable, "value")
|
||||
value.text = choices[0]
|
||||
variable.attrib["mandatory"] = "True"
|
||||
|
||||
# convert group to leadership
|
||||
groups = []
|
||||
|
|
@ -30,6 +30,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|||
from typing import List, Union
|
||||
from unicodedata import normalize, combining
|
||||
import re
|
||||
from itertools import chain
|
||||
|
||||
from importlib.machinery import SourceFileLoader
|
||||
from importlib.util import spec_from_loader, module_from_spec
|
||||
|
|
@ -37,7 +38,9 @@ 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 jinja2.nodes import Name, Getattr
|
||||
|
||||
from tiramisu.config import get_common_path
|
||||
|
||||
from .i18n import _
|
||||
from .error import DictConsistencyError
|
||||
|
|
@ -62,15 +65,16 @@ def normalize_family(family_name: str) -> str:
|
|||
"""replace space, accent, uppercase, ... by valid character"""
|
||||
if not family_name:
|
||||
return
|
||||
family_name = family_name.lower()
|
||||
family_name = family_name.replace("-", "_").replace(" ", "_").replace(".", "_")
|
||||
nfkd_form = normalize("NFKD", family_name)
|
||||
family_name = "".join([c for c in nfkd_form if not combining(c)])
|
||||
return family_name.lower()
|
||||
|
||||
|
||||
def load_modules(eosfunc_file) -> List[str]:
|
||||
"""list all functions in eosfunc"""
|
||||
loader = SourceFileLoader("eosfunc", eosfunc_file)
|
||||
def load_modules(name, module) -> List[str]:
|
||||
"""list all functions in a module"""
|
||||
loader = SourceFileLoader(name, module)
|
||||
spec = spec_from_loader(loader.name, loader)
|
||||
eosfunc = module_from_spec(spec)
|
||||
loader.exec_module(eosfunc)
|
||||
|
|
@ -87,11 +91,14 @@ def get_realpath(
|
|||
|
||||
|
||||
def get_jinja_variable_to_param(
|
||||
current_path: str,
|
||||
jinja_text,
|
||||
objectspace,
|
||||
xmlfiles,
|
||||
functions,
|
||||
path_prefix,
|
||||
version,
|
||||
namespace,
|
||||
):
|
||||
try:
|
||||
env = SandboxedEnvironment(loader=DictLoader({"tmpl": jinja_text}))
|
||||
|
|
@ -104,16 +111,58 @@ def get_jinja_variable_to_param(
|
|||
return g.node.name + "." + g.attr
|
||||
|
||||
variables = set()
|
||||
if objectspace.namespace is None:
|
||||
for n in parsed_content.find_all(Name):
|
||||
variables.add(n.name)
|
||||
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
|
||||
msg = _(f'error in jinja "{jinja_text}" for the variable "{ current_path }": {err}')
|
||||
raise DictConsistencyError(msg, 39, xmlfiles) from err
|
||||
variables = list(variables)
|
||||
variables.sort()
|
||||
variables.sort(reverse=True)
|
||||
founded_variables = {}
|
||||
unknown_variables = []
|
||||
for variable_path in variables:
|
||||
variable, suffix, dynamic = objectspace.paths.get_with_dynamic(
|
||||
get_realpath(variable_path, path_prefix)
|
||||
variable, suffix = objectspace.paths.get_with_dynamic(
|
||||
variable_path,
|
||||
path_prefix,
|
||||
current_path,
|
||||
version,
|
||||
namespace,
|
||||
xmlfiles,
|
||||
)
|
||||
if variable and variable.path in objectspace.variables:
|
||||
yield variable, suffix, variable_path, dynamic
|
||||
founded_variables[variable_path] = (suffix, variable)
|
||||
else:
|
||||
sub_family = variable_path + '.'
|
||||
for founded_variable in chain(founded_variables, unknown_variables):
|
||||
if founded_variable.startswith(sub_family):
|
||||
break
|
||||
else:
|
||||
unknown_variables.append(variable_path)
|
||||
|
||||
for variable_path in unknown_variables:
|
||||
for v in founded_variables:
|
||||
if get_common_path(v, variable_path) == v:
|
||||
break
|
||||
else:
|
||||
root_path = None
|
||||
vpath = variable_path
|
||||
while '.' in vpath:
|
||||
vpath = vpath.rsplit('.', 1)[0]
|
||||
variable, suffix = objectspace.paths.get_with_dynamic(
|
||||
vpath,
|
||||
path_prefix,
|
||||
current_path,
|
||||
version,
|
||||
namespace,
|
||||
xmlfiles,
|
||||
)
|
||||
if variable and variable.path in objectspace.families:
|
||||
root_path = vpath
|
||||
break
|
||||
if root_path:
|
||||
yield {}, None, root_path
|
||||
for variable_path, data in founded_variables.items():
|
||||
yield data[1], data[0], variable_path
|
||||
|
|
|
|||
5
tests/data/dict1/dict.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# dict with a correct version declared
|
||||
#version: "1.1"
|
||||
hello:
|
||||
type: string
|
||||
default: world
|
||||
5
tests/data/dict2/dict.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# dict with a correct version declared
|
||||
version: "1.0"
|
||||
hello:
|
||||
type: string
|
||||
default: world
|
||||