Compare commits

..

97 commits

Author SHA1 Message Date
adff39d4d3 bump: version 1.2.0a30 → 1.2.0a31 2025-09-22 14:36:42 +02:00
bfc59e8084 fix: dictionary => structure 2025-09-22 14:36:32 +02:00
e2d5a30c21 bump: version 1.2.0a29 → 1.2.0a30 2025-09-20 18:46:20 +02:00
9ee03b22bb feat(40): better conflict error message with dynamic name 2025-09-20 18:46:08 +02:00
c8d5656094 feat: add new get_root_option function 2025-08-28 21:24:45 +02:00
c4b90cbe42 fix: update test 2025-08-28 21:24:04 +02:00
62620d9b2e bump: version 1.2.0a28 → 1.2.0a29 2025-06-20 07:51:48 +03:00
bed7a1d3b5 fix: UserDatas, do now set modified option in second round 2025-06-20 07:51:35 +03:00
06c87718d6 bump: version 1.2.0a27 → 1.2.0a28 2025-06-18 16:50:18 +03:00
a0677e7b00 fix: conversion 2025-06-18 16:49:45 +03:00
c840d57734 bump: version 1.2.0a26 → 1.2.0a27 2025-06-18 07:49:44 +03:00
a3b2699289 feat: separate rougail and rougail-base 2025-06-18 07:49:39 +03:00
c7a66a034e bump: version 1.2.0a25 → 1.2.0a26 2025-05-26 08:11:43 +02:00
9e06f1142e fix: user_data better support for follower variable 2025-05-19 19:08:32 +02:00
b902e15243 bump: version 1.2.0a24 → 1.2.0a25 2025-05-14 08:44:37 +02:00
cbccc8087f feat: can launch UserDatas twice 2025-05-12 19:25:50 +02:00
fecab8aca6 bump: version 1.2.0a23 → 1.2.0a24 2025-05-12 09:13:57 +02:00
97cc2460b2 fix: upgrade translation 2025-05-12 08:52:59 +02:00
5a4bb343b0 fix: black 2025-05-12 08:45:39 +02:00
bb1f117ed1 bump: version 1.2.0a22 → 1.2.0a23 2025-05-09 08:21:40 +02:00
1662568c17 fix: add_quotes 2025-05-09 08:21:31 +02:00
6be773de3c fix: tiramisu_display_name could display only description 2025-05-08 22:10:28 +02:00
70b6f3d274 fix: use own undefined 2025-05-08 22:09:52 +02:00
e7a1caf113 fix: support of default_dictionary_format_version file in tests 2025-05-07 08:06:30 +02:00
b229154df5 fix: simplify version support 2025-05-06 08:09:37 +02:00
e71d4600d7 bump: version 1.2.0a21 → 1.2.0a22 2025-05-05 08:49:45 +02:00
adc580069c fix: user_datas support empty directory 2025-05-05 08:49:11 +02:00
9c940f8b28 bump: version 1.2.0a20 → 1.2.0a21 2025-05-02 08:12:31 +02:00
6167e1ea2c fix: support {{ suffix }} name in 1.1 format version 2025-05-02 08:12:06 +02:00
46e2ede81d fix: do not force use_data usage 2025-05-02 08:12:03 +02:00
2488cc471e fix: validators for an index 2025-05-01 21:27:19 +02:00
1dcc00429c bump: version 1.2.0a19 → 1.2.0a20 2025-04-30 09:02:51 +02:00
2f9e584394 fix: remove symlink 2025-04-30 09:02:16 +02:00
2027da0675 bump: version 1.2.0a18 → 1.2.0a19 2025-04-30 08:56:28 +02:00
efb23c350e fix: update translation 2025-04-29 23:12:32 +02:00
52c8277238 fix: remove negative_description support 2025-04-29 22:48:33 +02:00
3b0130420d fix: redefine family in flatten mode 2025-04-29 22:48:03 +02:00
00f29c37c4 feat: add ymlfile names in ConfigError message 2025-04-27 16:35:13 +02:00
b9ea5edb07 fix: update tests 2025-04-27 10:30:31 +02:00
136a0e71b7 fix: better multi check 2025-04-21 19:35:02 +02:00
fc56827706 bump: version 1.2.0a17 → 1.2.0a18 2025-04-09 21:19:54 +02:00
77490ca176 fix: version 2025-04-09 21:19:45 +02:00
65ffaef2fe bump: version 1.2.0a16 → 1.2.0a17 2025-04-09 09:02:38 +02:00
c494a4662f fix: better detection of multi variable in default attribute 2025-04-09 09:02:32 +02:00
0bc1e24d38 fix: better error message 2025-04-09 09:01:48 +02:00
e59b9e84df bump: version 1.2.0a15 → 1.2.0a16 2025-04-03 20:47:28 +02:00
62097b552a feat: reoganise param conversion + better variable validation 2025-04-03 20:47:12 +02:00
19249875c3 fix: correction in namespace calculation 2025-04-02 21:46:47 +02:00
ae89e30e60 bump: version 1.2.0a14 → 1.2.0a15 2025-04-01 22:21:37 +02:00
e1b2d36aae fix: update tests 2025-04-01 22:06:18 +02:00
cc086f086a fix: do not raise variable in property with force_optional 2025-04-01 21:10:22 +02:00
7ee65b6cab fix: update translation 2025-04-01 21:09:49 +02:00
1858eeeb06 feat: can link regexp variable 2025-04-01 08:55:00 +02:00
dc311466a2 feat: can link choice variable 2025-04-01 08:36:23 +02:00
18df35acfe bump: version 1.2.0a13 → 1.2.0a14 2025-03-30 18:42:35 +02:00
0e522db15f fix: strutural step should not be available in commandline 2025-03-30 18:42:13 +02:00
07a22d7966 bump: version 1.2.0a12 → 1.2.0a13 2025-03-30 18:38:33 +02:00
7f9d6f17d7 fix: allow no user_datas installation (for example to generate doc) 2025-03-27 20:05:20 +01:00
2654141002 bump: version 1.2.0a11 → 1.2.0a12 2025-03-19 11:43:13 +01:00
999b53889c feat: add rougail secret_manager 2025-03-19 11:43:04 +01:00
d35fe16cd9 bump: version 1.2.0a10 → 1.2.0a11 2025-02-17 15:45:33 +01:00
04aa9444a3 fix: add get remove properties 2025-02-17 15:45:27 +01:00
9c7fb1d505 bump: version 1.2.0a9 → 1.2.0a10 2025-02-17 09:21:02 +01:00
70003fcbc5 fix: we can define structural plugin when generate documentation 2025-02-17 09:20:38 +01:00
85f3b9f80f fix: if a variable in user_data not existe, it's no a warnings 2025-02-17 09:19:31 +01:00
b0687fdcc6 bump: version 1.2.0a8 → 1.2.0a9 2025-02-10 10:32:57 +01:00
cc5aaf6393 feat: can change defaut params for an option 2025-02-10 10:32:48 +01:00
df2fcb467f fix: if no description, generate negative_description too 2025-02-10 10:09:34 +01:00
7b9d7ce419 fix: error messages 2025-02-07 08:08:55 +01:00
63f76dc68f bump: version 1.2.0a7 → 1.2.0a8 2025-01-04 12:01:17 +01:00
e7e9687b8d fix: better support of not_for_commandline feature 2025-01-04 12:01:08 +01:00
0fea822c91 bump: version 1.2.0a6 → 1.2.0a7 2025-01-02 22:01:56 +01:00
ea6baa3dc2 fix: add structural_directory 2025-01-02 22:01:38 +01:00
7d830d5e40 bump: version 1.2.0a5 → 1.2.0a6 2025-01-02 21:19:40 +01:00
81d86e7b6f fix: add path.py 2025-01-02 21:19:16 +01:00
7a5a1b42e8 bump: version 1.2.0a4 → 1.2.0a5 2025-01-02 21:06:47 +01:00
3849c42cba feat: upgrade is not in formatter 2025-01-02 21:06:13 +01:00
6c1df5578f feat: remove prefix 2025-01-02 21:02:44 +01:00
dfd31c4fb4 bump: version 1.2.0a3 → 1.2.0a4 2024-12-11 21:00:22 +01:00
45959ac18b fix: remove link 2024-12-11 20:58:08 +01:00
9e8c6f96c5 feat: move test to a new project rougail-tests 2024-12-11 20:54:54 +01:00
00e0941f6e fix: reorganise user_datas 2024-12-11 20:54:03 +01:00
8afb787c98 fix: only change prefix if path is relative 2024-12-11 20:51:08 +01:00
a91a4d6a55 feat: output could have annotator 2024-12-11 20:50:15 +01:00
688aa8d053 bump: version 1.2.0a2 → 1.2.0a3 2024-11-28 08:31:00 +01:00
4c613f6425 fix: add user_datas file 2024-11-28 08:30:47 +01:00
145b7be0a5 bump: version 1.2.0a1 → 1.2.0a2 2024-11-27 16:09:18 +01:00
829d7692ad fix: separate UserDatas 2024-11-27 16:09:04 +01:00
ea34751e08 fix: options could be a list 2024-11-27 10:22:00 +01:00
c25a66f0cb feat: add "exists" attribut for a family 2024-11-27 10:14:42 +01:00
fdfb7ec73b bump: version 1.2.0a0 → 1.2.0a1 2024-11-25 09:18:04 +01:00
db768f94af fix: dynamic variable could be optional 2024-11-25 09:17:27 +01:00
9782bcde95 fix: dynamic variable declare in verion 1.0 has {{ suffix }} 2024-11-25 09:16:48 +01:00
fbb1446fa3 fix: user_data plugins could have annotator function 2024-11-25 09:14:58 +01:00
56659f7ea4 fix: do not modify a dynamic variable if has default value 2024-11-25 09:10:44 +01:00
af3f77c920 bump: version 1.1.1 → 1.2.0a0 2024-11-08 08:13:27 +01:00
e29c0c8abc feat: add force_optional option to allow charging structure even if all variables are not available 2024-11-08 08:12:33 +01:00
1603 changed files with 13618 additions and 12030 deletions

View file

@ -1,3 +1,249 @@
## 1.2.0a31 (2025-09-22)
### Fix
- dictionary => structure
## 1.2.0a30 (2025-09-20)
### Feat
- **40**: better conflict error message with dynamic name
- add new get_root_option function
### Fix
- update test
## 1.2.0a29 (2025-06-20)
### Fix
- UserDatas, do now set modified option in second round
## 1.2.0a28 (2025-06-18)
### Fix
- conversion
## 1.2.0a27 (2025-06-18)
### Feat
- separate rougail and rougail-base
## 1.2.0a26 (2025-05-26)
### Fix
- user_data better support for follower variable
## 1.2.0a25 (2025-05-14)
### Feat
- can launch UserDatas twice
## 1.2.0a24 (2025-05-12)
### Fix
- upgrade translation
- black
## 1.2.0a23 (2025-05-09)
### Fix
- add_quotes
- tiramisu_display_name could display only description
- use own undefined
- support of default_dictionary_format_version file in tests
- simplify version support
## 1.2.0a22 (2025-05-05)
### Fix
- user_datas support empty directory
## 1.2.0a21 (2025-05-02)
### Fix
- support {{ suffix }} name in 1.1 format version
- do not force use_data usage
- validators for an index
## 1.2.0a20 (2025-04-30)
### Fix
- remove symlink
## 1.2.0a19 (2025-04-30)
### Feat
- add ymlfile names in ConfigError message
### Fix
- update translation
- remove negative_description support
- redefine family in flatten mode
- update tests
- better multi check
## 1.2.0a18 (2025-04-09)
### Fix
- version
## 1.2.0a17 (2025-04-09)
### Fix
- better detection of multi variable in default attribute
- better error message
## 1.2.0a16 (2025-04-03)
### Feat
- reoganise param conversion + better variable validation
### Fix
- correction in namespace calculation
## 1.2.0a15 (2025-04-01)
### Feat
- can link regexp variable
- can link choice variable
### Fix
- update tests
- do not raise variable in property with force_optional
- update translation
## 1.2.0a14 (2025-03-30)
### Fix
- strutural step should not be available in commandline
## 1.2.0a13 (2025-03-27)
### Fix
- allow no user_datas installation (for example to generate doc)
## 1.2.0a12 (2025-03-19)
### Feat
- add rougail secret_manager
## 1.2.0a11 (2025-02-17)
### Fix
- add get remove properties
## 1.2.0a10 (2025-02-17)
### Fix
- we can define structural plugin when generate documentation
- if a variable in user_data not existe, it's no a warnings
## 1.2.0a9 (2025-02-10)
### Feat
- can change defaut params for an option
### Fix
- if no description, generate negative_description too
- error messages
## 1.2.0a8 (2025-01-04)
### Fix
- better support of not_for_commandline feature
## 1.2.0a7 (2025-01-02)
### Fix
- add structural_directory
## 1.2.0a6 (2025-01-02)
### Fix
- add path.py
## 1.2.0a5 (2025-01-02)
### Feat
- upgrade is not in formatter
- remove prefix
## 1.2.0a4 (2024-12-11)
### Feat
- move test to a new project rougail-tests
- output could have annotator
### Fix
- remove link
- reorganise user_datas
- only change prefix if path is relative
## 1.2.0a3 (2024-11-28)
### Fix
- add user_datas file
## 1.2.0a2 (2024-11-27)
### Feat
- add "exists" attribut for a family
### Fix
- separate UserDatas
- options could be a list
## 1.2.0a1 (2024-11-25)
### Fix
- dynamic variable could be optional
- dynamic variable declare in verion 1.0 has {{ suffix }}
- user_data plugins could have annotator function
- do not modify a dynamic variable if has default value
## 1.2.0a0 (2024-11-08)
### Feat
- add force_optional option to allow charging structure even if all variables are not available
## 1.1.1 (2024-11-06)
### Fix

View file

@ -5,8 +5,8 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2024-10-30 13:21+0100\n"
"PO-Revision-Date: 2024-10-30 13:39+0100\n"
"POT-Creation-Date: 2025-05-12 08:45+0200\n"
"PO-Revision-Date: 2025-05-12 08:52+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: fr\n"
@ -16,39 +16,39 @@ msgstr ""
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 3.5\n"
#: src/rougail/annotator/family.py:142
#: src/rougail/annotator/family.py:150
msgid "default variable mode \"{0}\" is not a valid mode, valid modes are {1}"
msgstr ""
"le mode d'une variable par défaut \"{0}\" n'est pas un mode valide, les "
"modes valides sont {1}"
#: src/rougail/annotator/family.py:148
#: src/rougail/annotator/family.py:156
msgid "default family mode \"{0}\" is not a valid mode, valid modes are {1}"
msgstr ""
"le mode d'une famille par défaut \"{0}\" n'est pas un mode valide, les modes "
"valides sont {1}"
#: src/rougail/annotator/family.py:180
#: src/rougail/annotator/family.py:188
msgid "mode \"{0}\" for \"{1}\" is not a valid mode, valid modes are {2}"
msgstr ""
"le mode \"{0}\" pour \"{1}\" n'est pas un mode valide, les modes valides "
"sont {2}"
#: src/rougail/annotator/family.py:184
#: src/rougail/annotator/family.py:192
msgid "mode \"{0}\" for \"{1}\" is not a valid mode, no modes are available"
msgstr ""
"le mode \"{0}\" pour \"{1}\" n'est pas un mode valide, aucun mode ne sont "
"définis"
#: src/rougail/annotator/family.py:248
#: src/rougail/annotator/family.py:256
msgid ""
"the variable \"{0}\" is mandatory so in \"{1}\" mode but family has the "
"higher family mode \"{2}\""
msgstr ""
"la variable \"{0}\" est obligatoire donc dans le mode \"{1}\" mais la "
"famille a un mode supérieur \"{2}\""
"la variable \"{0}\" est obligatoire, donc en mode \"{1}\", mais la famille a "
"un mode supérieur \"{2}\""
#: src/rougail/annotator/family.py:286
#: src/rougail/annotator/family.py:294
msgid ""
"the follower \"{0}\" is in \"{1}\" mode but leader have the higher mode "
"\"{2}\""
@ -56,7 +56,7 @@ msgstr ""
"la variable suiveuse \"{0}\" a le mode \"{1}\" mais la variable leader a un "
"mode supérieur \"{2}\""
#: src/rougail/annotator/family.py:319
#: src/rougail/annotator/family.py:327
msgid ""
"the family \"{0}\" is in \"{1}\" mode but variables and families inside have "
"the higher modes \"{2}\""
@ -64,7 +64,7 @@ msgstr ""
"la famille \"{0}\" a le mode \"{1}\" mais les variables et les familles à "
"l'intérieur ont des modes supérieurs \"{2}\""
#: src/rougail/annotator/family.py:337
#: src/rougail/annotator/family.py:345
msgid ""
"the variable \"{0}\" is in \"{1}\" mode but family has the higher family "
"mode \"{2}\""
@ -72,37 +72,369 @@ msgstr ""
"la variable \"{0}\" est dans le mode \"{1}\" mais la famille a le mode "
"supérieur \"{2}\""
#: src/rougail/annotator/value.py:80
msgid "the follower \"{0}\" without multi attribute can only have one value"
#: src/rougail/annotator/value.py:78
msgid ""
"the follower \"{0}\" is not multi, so cannot have a list has default value"
msgstr ""
"la variable suiveuse \"{0}\" sans l'attribut multi peut avoir seulement une "
"valeur"
"la variable suiveuse \"{0}\" n'est pas multiple, donc ne peut avoir une "
"liste comme valeur par défaut"
#: src/rougail/annotator/value.py:96
#: src/rougail/annotator/value.py:94
msgid "the variable \"{0}\" is multi but has a non list default value"
msgstr ""
"la variable \"{0}\" est multiple mais a une valeur par défaut sans être une "
"liste"
#: src/rougail/annotator/variable.py:192
#: src/rougail/annotator/value.py:118
msgid "the variable \"{0}\" is a \"choice\" variable but don't have any choice"
msgstr "la variable \"{0}\" a une variable à \"choix\" mais n'a aucun choix"
#: src/rougail/annotator/value.py:137
msgid "the variable \"{0}\" is a \"regexp\" variable but don't have any regexp"
msgstr ""
"la variable \"{0}\" a une variable \"regexp\" mais n'a pas de \"regexp\""
#: src/rougail/annotator/variable.py:86
msgid ""
"only \"unix_user\" or \"secret\" variable type can have \"secret_manager\" "
"attribute, but \"{0}\" has type \"{1}\""
msgstr ""
"seul une variable de type \"unix_user\" ou \"secret\" peut avoir l'attribut "
"\"secret_manager\", mais \"{0}\" a le type \"{1}\""
#: src/rougail/annotator/variable.py:93
msgid ""
"the variable \"{0}\" has attribute \"secret_manager\" but is a multi variable"
msgstr ""
"la variable \"{0}\" a l'attribut \"secret_manager\" mais est une variable "
"multiple"
#: src/rougail/annotator/variable.py:98
msgid ""
"the variable \"{0}\" has attribute \"secret_manager\" so must not have "
"default value"
msgstr ""
"la variable \"{0}\" a l'attribut \"secret_manager\" donc ne devrait pas "
"avoir de valeur par défaut"
#: src/rougail/annotator/variable.py:227
msgid ""
"the variable \"{0}\" has regexp attribut but has not the \"regexp\" type"
msgstr ""
"la variable \"{0}\" a un attribut regexp mais n'a pas le type \"regexp\""
#: src/rougail/annotator/variable.py:235
#: src/rougail/annotator/variable.py:270
msgid ""
"the variable \"{0}\" has choices attribut but has not the \"choice\" type"
msgstr ""
"la variable \"{0}\" a un attribut choices mais n'a pas le type \"choice\""
#: src/rougail/annotator/variable.py:263
#: src/rougail/annotator/variable.py:298
msgid ""
"the variable \"{0}\" has an unvalid default value \"{1}\" should be in {2}"
msgstr ""
"la variable \"{0}\" a la valeur par défaut invalide \"{1}\" devrait être {2}"
#: src/rougail/convert.py:268
#: src/rougail/config.py:226
msgid "Structure format version by default, if not specified in structure file"
msgstr ""
"La version du format de la structure par défaut, si non spécifier dans le "
"fichier de structure"
#: src/rougail/config.py:234
msgid "File with functions"
msgstr "Fichier avec les fonctions"
#: src/rougail/config.py:246
msgid "All modes level available"
msgstr "Tous les niveaux de modes valides"
#: src/rougail/config.py:258
msgid "Default mode for a family"
msgstr "Mode par défaut pour une famille"
#: src/rougail/config.py:278
msgid "Default mode for a variable"
msgstr "Mode par défaut pour une variable"
#: src/rougail/config.py:302
msgid "Option name for the base option"
msgstr "Nom de l'option pour l'option de base"
#: src/rougail/config.py:307
msgid "In cache file, do not importation of Tiramisu and other dependencies"
msgstr ""
"Dans le fichier de cache, ne pas importer Tiramisu et autres dépendances"
#: src/rougail/config.py:312
msgid "Tiramisu cache filename"
msgstr "Nom du fichier du cache Tiramisu"
#: src/rougail/config.py:320
msgid "Name of internal functions that we can use as a function"
msgstr ""
"Nom des fonctions internes qu'il est possible d'utiliser comme fonction"
#: src/rougail/config.py:326
msgid "Name of extra annotators"
msgstr "Nom des annotators supplémentaires"
#: src/rougail/config.py:332
msgid "Suffix add to generated options name"
msgstr "Suffix ajouté pour généré le nom des options"
#: src/rougail/config.py:338
msgid "Every variables in calculation are optionals"
msgstr "Toutes les variables dans un calcul sont optionnelles"
#: src/rougail/config.py:342
msgid "Loads redefine variables even if there don't already exists"
msgstr "Charger les variables redéfinis même si elles n'existe pas"
#: src/rougail/config.py:349
msgid "The secret pattern to build item name in Bitwarden"
msgstr "Le patron de secret pour construire le nom de l'élément dans Bitwarden"
#: src/rougail/config.py:350
msgid "The pattern is in Jinja format"
msgstr "Le patron est au format Jinja"
#: src/rougail/config.py:378
msgid "Select for {0}"
msgstr "Sélection pour {0}"
#: src/rougail/config.py:458
msgid "Override default parameters for option type"
msgstr "Sur charger les paramètre par défaut pour le type d'option"
#: src/rougail/config.py:461
msgid "Default parameters for option type"
msgstr "Paramètre par défaut pour le type d'option"
#: src/rougail/convert.py:280
msgid "unknown type {0} for {1}"
msgstr "type {0} inconnu pour {1}"
#: src/rougail/convert.py:411
msgid "family \"{0}\" define multiple time"
msgstr "la famille \"{0}\" est définit plusieurs fois"
#: src/rougail/convert.py:668
msgid "variable \"{0}\" define multiple time"
msgstr "la variable \"{0}\" est définit plusieurs fois"
#: src/rougail/convert.py:766
msgid "params must be a dict for {0}"
msgstr "params doit être une dict pour {0}"
#: src/rougail/convert.py:787
msgid "\"{0}\" has an invalid \"params\" for {1}: {2}"
msgstr "\"{0}\" a un attribut \"params\" invalide pour {1}: {2}"
#: src/rougail/convert.py:801
msgid "secret_manager must be a dict for {0}"
msgstr "secret_manager doit être une dict pour {0}"
#: src/rougail/convert.py:1129
msgid "Cannot execute annotate multiple time"
msgstr "Ne peut exécuter l'annotation plusieurs fois"
#: src/rougail/convert.py:1136
msgid ""
"invalid \"structural\" definition ({0}), we cannot load any structural file!"
msgstr ""
"définition invalide pour la définition des \"structures\" ({0}), aucun "
"fichier de structure ne peut être chargé !"
#: src/rougail/error.py:67 src/rougail/tiramisu.py:60
#: src/rougail/user_datas.py:354 src/rougail/user_datas.py:357
msgid "{0} in {1}"
msgstr "{0} dans {1}"
#: src/rougail/object_model.py:50
msgid "unknown boolean value \"{0}\""
msgstr "valeur du booléen inconnue \"{0}\""
#: src/rougail/object_model.py:187
msgid "cannot find variable \"{0}\" defined in attribute \"{1}\" for \"{2}\""
msgstr ""
"ne peut trouver la variable \"{0}\" défini dans l'attribut \"{1}\" pour "
"\"{2}\""
#: src/rougail/object_model.py:193
msgid ""
"the variable \"{0}\" is in fact a family in attribute \"{1}\" for \"{2}\""
msgstr ""
"la variable \"{0}\" est en faite une famille dans l'attribut \"{1}\" pour "
"\"{2}\""
#: src/rougail/object_model.py:198 src/rougail/object_model.py:525
msgid "unknown object \"{0}\" in attribute \"{1}\" for \"{2}\""
msgstr "objet inconnu \"{0}\" dans l'attribut \"{1}\" pour \"{2}\""
#: src/rougail/object_model.py:217
msgid ""
"identifier parameter for \"{0}\" in \"{1}\" cannot be set none dynamic family"
msgstr ""
"le paramètre identifier pour \"{0}\" dans \"{1}\" ne peut être placé pour "
"une famille non dynamique"
#: src/rougail/object_model.py:246
msgid "cannot find variable \"{0}\" defined in \"{1}\" for \"{2}\""
msgstr "ne peut trouver la variable \"{0}\" défini dans \"{1}\" pour \"{2}\""
#: src/rougail/object_model.py:251
msgid "variable \"{0}\" defined in \"{1}\" for \"{2}\" is a dynamic variable"
msgstr ""
"la variable \"{0}\" défini dans \"{1}\" pour \"{2}\" est une variable "
"dynamique"
#: src/rougail/object_model.py:268
msgid ""
"the variable \"{0}\" is not a follower, so cannot have index type for param "
"in \"{1}\""
msgstr ""
"la variable \"{0}\" n'est pas suiveuse, donc ne peut avoir de type index "
"comme paramètre dans \"{1}\""
#: src/rougail/object_model.py:520
msgid ""
"a variable \"{0}\" is needs in attribute \"{1}\" for \"{2}\" but it's a "
"family"
msgstr ""
"une variable \"{0}\" est nécessaire pour l'attribut \"{1}\" pour \"{2}\" "
"mais c'est une famille"
#: src/rougail/object_model.py:544
msgid ""
"variable \"{0}\" has an attribute \"{1}\" calculated with the unknown "
"variable \"{2}\""
msgstr ""
"la variable \"{0}\" a un attribut invalide \"{1}\" calculé avec la variable "
"inconnue \"{2}\""
#: src/rougail/object_model.py:593
msgid ""
"the variable \"{0}\" has an invalid \"{1}\" the variable \"{2}\" is in a sub "
"dynamic option"
msgstr ""
"la variable \"{0}\" a un attribut invalide \"{1}\", la variable \"{2}\" est "
"dans une sous option dynamique"
#: src/rougail/object_model.py:602
msgid ""
"the leader \"{0}\" has an invalid \"{1}\" the follower \"{2}\" is a multi"
msgstr ""
"la variable meneuse \"{0}\" a un attribut invalide \"{1}\", la variable "
"suiveuse \"{2}\" est multiple"
#: src/rougail/object_model.py:644
msgid ""
"the variable \"{0}\" has an invalid attribute \"{1}\", the variable \"{2}\" "
"must not be multi"
msgstr ""
"la variable \"{0}\" a un attribut invalide \"{1}\", la variable \"{2}\" ne "
"doit pas être multiple"
#: src/rougail/object_model.py:658
msgid ""
"the variable \"{0}\" has an invalid attribute \"{1}\", the variable must not "
"be a multi or the variable \"{2}\" must be multi"
msgstr ""
"la variable \"{0}\" a un attribut invalide \"{1}\", la variable ne doit pas "
"être multiple ou la variable \"{2}\" doit être multiple"
#: src/rougail/object_model.py:672
msgid ""
"the variable \"{0}\" has an invalid attribute \"{1}\", the variable must be "
"a multi or the variable \"{2}\" must not be multi"
msgstr ""
"la variable \"{0}\" a un attribut invalide \"{1}\", la variable doit être "
"multiple ou la variable \"{2}\" ne doit pas être multiple"
#: src/rougail/object_model.py:686
msgid ""
"the variable \"{0}\" has an invalid attribute \"{1}\", the variable \"{2}\" "
"is multi but is inside a list"
msgstr ""
"la variable \"{0}\" a un attribut invalide \"{1}\", la variable \"{2}\" est "
"multiple mais est dans une liste"
#: src/rougail/object_model.py:704
msgid ""
"\"{0}\" attribut shall not have an \"optional\" attribute for variable "
"\"{1}\""
msgstr ""
"l'attribut \"{0}\" ne devrait pas avoir d'attribut \"optional\" pour la "
"variable \"{1}\""
#: src/rougail/object_model.py:724
msgid ""
"variable \"{0}\" has a default value calculated with \"{1}\" which has "
"incompatible type"
msgstr ""
"la variable \"{0}\" a une valeur par défaut calculé a partir de \"{1}\" "
"laquelle a un type incompatible"
#: src/rougail/object_model.py:770
msgid ""
"\"when\" is not allowed in format version 1.0 for attribute \"{0}\" for "
"variable \"{1}\""
msgstr ""
"\"when\" n'est pas autorisé avec le format en version 1.0 pour l'attribut "
"\"{0}\" pour la variable \"{1}\""
#: src/rougail/object_model.py:775
msgid ""
"the variable \"{0}\" has an invalid attribute \"{1}\", \"when\" and "
"\"when_not\" cannot set together"
msgstr ""
"la variable \"{0}\" a un attribut invalide \"{1}\", \"when\" et \"when_not\" "
"ne peuvent pas être défini ensemble"
#: src/rougail/object_model.py:783
msgid ""
"\"when_not\" is not allowed in format version 1.0 for attribute \"{0}\" for "
"variable \"{1}\""
msgstr ""
"\"when_not\" n'est pas autorisé au format 1.0 pour l'attribut \"{0}\" pour "
"la variable variable \"{1}\""
#: src/rougail/object_model.py:840
msgid ""
"cannot find variable \"{0}\" for the information \"{1}\" when calculating "
"\"{2}\""
msgstr ""
"ne peut trouver la variable \"{0}\" pour l'information \"{1}\" lors du "
"calcul de \"{2}\""
#: src/rougail/object_model.py:845
msgid ""
"identifier not allowed for the information \"{0}\" when calculating \"{1}\""
msgstr ""
"identifier n'est pas autorisé pour l'information \"{0}\" lors du calcul de "
"\"{1}\""
#: src/rougail/object_model.py:898
msgid "\"when\" is not allowed in format version 1.0 for attribute \"{0}\""
msgstr "\"when\" n'est pas autorisé au format 1.0 pour l'attribut \"{0}\""
#: src/rougail/object_model.py:904 src/rougail/object_model.py:914
msgid ""
"the identifier has an invalid attribute \"{0}\", \"when\" and \"when_not\" "
"cannot set together"
msgstr ""
"l'identifiant a un attribut invalide \"{0}\", \"when\" et \"when_not\" ne "
"peuvent pas être défini ensemble"
#: src/rougail/object_model.py:939
msgid ""
"the variable \"{0}\" is not a follower, so cannot have index type for \"{1}\""
msgstr ""
"la variable \"{0}\" n'est pas suiveuse, donc ne peut avoir de type index "
"pour \"{1}\""
#: src/rougail/path.py:207
msgid ""
"A variable or a family located in the \"{0}\" namespace shall not be used in "
"the \"{1}\" namespace"
@ -110,50 +442,142 @@ msgstr ""
"Une variable ou une famille localisé dans l'espace de nom \"{0}\" ne devrait "
"pas être utilisé dans l'espace de nom \"{1}\""
#: src/rougail/convert.py:462
msgid "unknown type {0} for {1}"
msgstr "type {0} inconnu pour {1}"
#: src/rougail/convert.py:1323
msgid "duplicate dictionary file name {0}"
msgstr "nom de fichier {0} de dictionnaire dupliqué"
#: src/rougail/convert.py:1370
msgid "Cannot execute annotate multiple time"
msgstr "Ne peut exécuter l'annotation plusieurs fois"
#: src/rougail/error.py:70
msgid "{0} in {1}"
msgstr "{0} dans {1}"
#: src/rougail/structural_commandline/annotator.py:70
#: src/rougail/structural_commandline/annotator.py:66
msgid "alternative_name \"{0}\" conflict with \"--help\""
msgstr "alternative_name \"{0}\" est en conflit avec \"--help\""
#: src/rougail/structural_commandline/annotator.py:73
#: src/rougail/structural_commandline/annotator.py:71
msgid "conflict alternative_name \"{0}\": \"{1}\" and \"{2}\""
msgstr "conflit dans les \"alternative_name\" \"{0}\": \"{1}\" et \"{2}\""
#: src/rougail/structural_commandline/annotator.py:96
msgid ""
"negative_description is mandatory for boolean variable, but \"{0}\" hasn't"
#: src/rougail/structural_directory/__init__.py:127
msgid "duplicate dictionary file name {0}"
msgstr "nom de fichier {0} de dictionnaire dupliqué"
#: src/rougail/structural_directory/config.py:32
msgid "Directories where dictionary files are placed"
msgstr "Répertoires où sont placés les fichiers de structure"
#: src/rougail/structural_directory/config.py:49
msgid "Sort dictionaries from differents directories"
msgstr "Trier les fichiers de structure à partir de différents répertoires"
#: src/rougail/structural_directory/config.py:58
msgid "Main namespace name"
msgstr "Nom de l'espace de nom principal"
#: src/rougail/structural_directory/config.py:69
msgid "Extra namespaces"
msgstr "Espaces de nom supplémentaires"
#: src/rougail/structural_directory/config.py:78
msgid "Extra namespace name"
msgstr "Nom de l'espace de nom supplémentaire"
#: src/rougail/structural_directory/config.py:84
msgid "Directories where extra dictionary files are placed"
msgstr ""
"l'attribut negative_description est obligatoire pour des variables "
"\"boolean\", mais \"{0}\" n'en a pas"
"Répertoires où sont placés les fichiers de structure de l'espace de nom "
"supplémentaire"
#: src/rougail/structural_commandline/annotator.py:105
#: src/rougail/tiramisu.py:187
msgid ""
"negative_description is only available for boolean variable, but \"{0}\" is "
"\"{1}\""
"cannot calculating \"{0}\" attribute for variable \"{1}\" in {2} with "
"parameters \"{3}\": {4}"
msgstr ""
"l'attribut negative_description est seulement valide pour des variables "
"\"boolean\", mais \"{0}\" est \"{1}\""
"ne peut calculer l'attribut \"{0}\" pour la variable \"{1}\" dans {2} avec "
"les paramètres \"{3}\" : {4}"
#: src/rougail/update/update.py:741
msgid "not a XML file: {0}"
msgstr "fichier XML invalid : {0}"
#: src/rougail/tiramisureflector.py:363
msgid "internal error, {0} is not a dynamic variable"
msgstr "erreur interne, \"{0}\" n'est pas une variable dynamique"
#: src/rougail/utils.py:58
#: src/rougail/user_datas.py:118
msgid ""
"cannot load variable path \"{0}\", the identifier \"{1}\" is not valid in {2}"
msgstr ""
"ne peut charger la variable \"{0}\", l'identifiant \"{1}\" n'est pas valide "
"dans {2}"
#: src/rougail/user_datas.py:196
msgid "the variable \"{0}\" contains secrets and should not be defined in {1}"
msgstr ""
"la variable \"{0}\" contient des secrets et ne devrait pas être défini dans "
"{1}"
#: src/rougail/user_datas.py:218
msgid "loaded from {0}"
msgstr "chargée depuis {0}"
#: src/rougail/user_datas.py:258
msgid ""
"cannot set the value \"{0}\" to the family {1}, it will be ignored when "
"loading from {2}"
msgstr ""
"impossible de définir la valeur \"{0}\" à la famille {1}, elle sera ignorée "
"lors du chargement depuis {0}"
#: src/rougail/user_datas.py:270
msgid ""
"variable or family \"{0}\" does not exist, it will be ignored when loading "
"from {1}"
msgstr ""
"la variable ou la famille \"{0}\" n'existe pas, elle sera ignorée lors du "
"chargement depuis {1}"
#: src/rougail/user_datas.py:276
msgid ""
"\"{0}\" is the name of a dynamic family, it will be ignored when loading "
"from {1}"
msgstr ""
"\"{0}\" est le nom d'une famille dynamique, il sera ignoré lors du "
"chargement depuis {1}"
#: src/rougail/user_datas.py:282
msgid "{0} loaded from {1}"
msgstr "{0} chargée depuis {1}"
#: src/rougail/user_datas.py:311
msgid ""
"variable {0} at index \"{1}\" is {2}, it will be ignored when loading from "
"{3}"
msgstr ""
"la variable {0} à l'index \"{1}\" est {2}, elle sera ignorée lors du "
"chargement depuis {3}"
#: src/rougail/user_datas.py:322
msgid ""
"family {0} is {1}, {2} at index \"{3}\" will be ignored when loading from {4}"
msgstr ""
"la famille {0} est {1}, {2} à l'index \"{3}\" sera ignorée lors du "
"chargement depuis {4}"
#: src/rougail/user_datas.py:335
msgid "variable {0} is {1}, it will be ignored when loading from {2}"
msgstr ""
"la variable {0} est {1}, elle sera ignorée lors du chargement depuis {2}"
#: src/rougail/user_datas.py:343
msgid "family {0} is {1}, {2} will be ignored when loading from {3}"
msgstr "la famille {0} est {1}, {2} sera ignorée lors du chargement depuis {3}"
#: src/rougail/user_datas.py:362
msgid ""
"the value \"{0}\" is invalid for {1} at index \"{2}\", {3}, it will be "
"ignored when loading from {4}"
msgstr ""
"la valeur \"{0}\" n'est pas valide pour {1} à l'index \"{2}\", {3}, elle "
"sera ignorée lors du chargement depuis {4}"
#: src/rougail/user_datas.py:373
msgid ""
"the value \"{0}\" is invalid for {1}, {2}, it will be ignored when loading "
"from {3}"
msgstr ""
"la valeur \"{0}\" n'est pas valide pour {1}, {2}, elle sera ignorée lors du "
"chargement depuis {3}"
#: src/rougail/utils.py:55
msgid ""
"invalid variable or family name \"{0}\" must only contains lowercase ascii "
"character, number or _"
@ -161,6 +585,85 @@ msgstr ""
"nom invalide pour la variable ou famille \"{0}\" doit seulement contenir des "
"caractères ascii minuscule, nombre or _"
#: src/rougail/utils.py:120
#: src/rougail/utils.py:113
msgid "error in jinja \"{0}\" for the variable \"{1}\": {2}"
msgstr "erreur dans Jinja \"{0}\" pour la variable \"{1}\": {2}"
#~ msgid ""
#~ "the variable \"{0}\" is a family, so cannot set the value \"{1}\" in {2}"
#~ msgstr ""
#~ "la variable \"{0}\" est une famille, donc ne peut avoir l'avoir \"{1}\" "
#~ "dans {2}"
#~ msgid "the follower \"{0}\" without multi attribute can only have one value"
#~ msgstr ""
#~ "la variable suiveuse \"{0}\" sans l'attribut multi peut avoir seulement "
#~ "une valeur"
#~ msgid "In cache file, do importation of Tiramisu and other dependencies"
#~ msgstr "Dans le fichier de cache, importer Tiramisu et autres dépendances"
#~ msgid "Variables in calculation are not optional by default"
#~ msgstr "Les variables dans un calcul sont optionnelles par défaut"
#~ msgid "Variable not found \"{0}\" for attribut \"{1}\" in variable \"{2}\""
#~ msgstr ""
#~ "La variable \"{0}\" n'est pas trouvé pour l'attribut \"{1}\" dans la "
#~ "variable \"{2}\""
#~ msgid ""
#~ "the variable \"{0}\" has an invalid attribute \"{1}\", the variable "
#~ "\"{2}\" is not multi but is not inside a list"
#~ msgstr ""
#~ "la variable \"{0}\" a un attribut invalide \"{1}\", la variable \"{2}\" "
#~ "n'est pas multiple mais n'est pas dans une liste"
#~ msgid "the variable \"{0}\" has an invalid attribute \"{1}\", it's a list"
#~ msgstr "la variable \"{0}\" a un attribut invalide \"{1}\", c'est une liste"
#~ msgid ""
#~ "negative_description is mandatory for boolean variable, but \"{0}\" hasn't"
#~ msgstr ""
#~ "l'attribut negative_description est obligatoire pour des variables "
#~ "\"boolean\", mais \"{0}\" n'en a pas"
#~ msgid ""
#~ "negative_description is only available for boolean variable, but \"{0}\" "
#~ "is \"{1}\""
#~ msgstr ""
#~ "l'attribut negative_description est seulement valide pour des variables "
#~ "\"boolean\", mais \"{0}\" est \"{1}\""
#~ msgid "error in {0}: {1}"
#~ msgstr "erreur dans {0} : {1}"
#~ msgid "cannot find variable \"{0}\" from {1}: {2}"
#~ msgstr "ne peut trouver la variable \"{0}\" depuis {1} : {2}"
#~ msgid "the option \"{0}\" is an option description"
#~ msgstr "l'option \"{0}\" est une option description"
#~ msgid "Update dictionaries to newest Rougail format version"
#~ msgstr ""
#~ "Mettre à jour le fichier de structure vers la dernière version du format "
#~ "de Rougail"
#~ msgid "Do not update dictionaries to newest Rougail format version"
#~ msgstr ""
#~ "Ne pas mettre à jour le fichier de structure vers la dernière version du "
#~ "format de Rougail"
#~ msgid "Update informations"
#~ msgstr "Mise à jour des informations"
#~ msgid "Directories where dictionary files will be placed"
#~ msgstr "Répertoires où sont placés les fichiers de structure"
#~ msgid "Directories where extra files will be placed"
#~ msgstr "Répertoires où sont placés les fichiers de structure supplémentaire"
#~ msgid "The family \"{0}\" already exists and it is not redefined"
#~ msgstr "La famille \"{0}\" existe déjà et n'est pas redéfinie"
#~ msgid "not a XML file: {0}"
#~ msgstr "fichier XML invalid : {0}"

View file

@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2024-11-04 12:04+0100\n"
"POT-Creation-Date: 2025-05-12 08:52+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -15,103 +15,396 @@ msgstr ""
"Generated-By: pygettext.py 1.5\n"
#: src/rougail/annotator/family.py:139
#: src/rougail/annotator/family.py:150
msgid "default variable mode \"{0}\" is not a valid mode, valid modes are {1}"
msgstr ""
#: src/rougail/annotator/family.py:145
#: src/rougail/annotator/family.py:156
msgid "default family mode \"{0}\" is not a valid mode, valid modes are {1}"
msgstr ""
#: src/rougail/annotator/family.py:177
#: src/rougail/annotator/family.py:188
msgid "mode \"{0}\" for \"{1}\" is not a valid mode, valid modes are {2}"
msgstr ""
#: src/rougail/annotator/family.py:181
#: src/rougail/annotator/family.py:192
msgid "mode \"{0}\" for \"{1}\" is not a valid mode, no modes are available"
msgstr ""
#: src/rougail/annotator/family.py:245
#: src/rougail/annotator/family.py:256
msgid "the variable \"{0}\" is mandatory so in \"{1}\" mode but family has the higher family mode \"{2}\""
msgstr ""
#: src/rougail/annotator/family.py:283
#: src/rougail/annotator/family.py:294
msgid "the follower \"{0}\" is in \"{1}\" mode but leader have the higher mode \"{2}\""
msgstr ""
#: src/rougail/annotator/family.py:316
#: src/rougail/annotator/family.py:327
msgid "the family \"{0}\" is in \"{1}\" mode but variables and families inside have the higher modes \"{2}\""
msgstr ""
#: src/rougail/annotator/family.py:334
#: src/rougail/annotator/family.py:345
msgid "the variable \"{0}\" is in \"{1}\" mode but family has the higher family mode \"{2}\""
msgstr ""
#: src/rougail/annotator/value.py:77
msgid "the follower \"{0}\" without multi attribute can only have one value"
#: src/rougail/annotator/value.py:78
msgid "the follower \"{0}\" is not multi, so cannot have a list has default value"
msgstr ""
#: src/rougail/annotator/value.py:93
#: src/rougail/annotator/value.py:94
msgid "the variable \"{0}\" is multi but has a non list default value"
msgstr ""
#: src/rougail/annotator/variable.py:189
#: src/rougail/annotator/value.py:118
msgid "the variable \"{0}\" is a \"choice\" variable but don't have any choice"
msgstr ""
#: src/rougail/annotator/value.py:137
msgid "the variable \"{0}\" is a \"regexp\" variable but don't have any regexp"
msgstr ""
#: src/rougail/annotator/variable.py:86
msgid "only \"unix_user\" or \"secret\" variable type can have \"secret_manager\" attribute, but \"{0}\" has type \"{1}\""
msgstr ""
#: src/rougail/annotator/variable.py:93
msgid "the variable \"{0}\" has attribute \"secret_manager\" but is a multi variable"
msgstr ""
#: src/rougail/annotator/variable.py:98
msgid "the variable \"{0}\" has attribute \"secret_manager\" so must not have default value"
msgstr ""
#: src/rougail/annotator/variable.py:227
msgid "the variable \"{0}\" has regexp attribut but has not the \"regexp\" type"
msgstr ""
#: src/rougail/annotator/variable.py:232
#: src/rougail/annotator/variable.py:270
msgid "the variable \"{0}\" has choices attribut but has not the \"choice\" type"
msgstr ""
#: src/rougail/annotator/variable.py:260
#: src/rougail/annotator/variable.py:298
msgid "the variable \"{0}\" has an unvalid default value \"{1}\" should be in {2}"
msgstr ""
#: src/rougail/convert.py:281
msgid "A variable or a family located in the \"{0}\" namespace shall not be used in the \"{1}\" namespace"
#: src/rougail/config.py:226
msgid "Structure format version by default, if not specified in structure file"
msgstr ""
#: src/rougail/convert.py:475
#: src/rougail/config.py:234
msgid "File with functions"
msgstr ""
#: src/rougail/config.py:246
msgid "All modes level available"
msgstr ""
#: src/rougail/config.py:258
msgid "Default mode for a family"
msgstr ""
#: src/rougail/config.py:278
msgid "Default mode for a variable"
msgstr ""
#: src/rougail/config.py:302
msgid "Option name for the base option"
msgstr ""
#: src/rougail/config.py:307
msgid "In cache file, do not importation of Tiramisu and other dependencies"
msgstr ""
#: src/rougail/config.py:312
msgid "Tiramisu cache filename"
msgstr ""
#: src/rougail/config.py:320
msgid "Name of internal functions that we can use as a function"
msgstr ""
#: src/rougail/config.py:326
msgid "Name of extra annotators"
msgstr ""
#: src/rougail/config.py:332
msgid "Suffix add to generated options name"
msgstr ""
#: src/rougail/config.py:338
msgid "Every variables in calculation are optionals"
msgstr ""
#: src/rougail/config.py:342
msgid "Loads redefine variables even if there don't already exists"
msgstr ""
#: src/rougail/config.py:349
msgid "The secret pattern to build item name in Bitwarden"
msgstr ""
#: src/rougail/config.py:350
msgid "The pattern is in Jinja format"
msgstr ""
#: src/rougail/config.py:378
msgid "Select for {0}"
msgstr ""
#: src/rougail/config.py:458
msgid "Override default parameters for option type"
msgstr ""
#: src/rougail/config.py:461
msgid "Default parameters for option type"
msgstr ""
#: src/rougail/convert.py:280
msgid "unknown type {0} for {1}"
msgstr ""
#: src/rougail/convert.py:1345
msgid "duplicate dictionary file name {0}"
#: src/rougail/convert.py:411
msgid "family \"{0}\" define multiple time"
msgstr ""
#: src/rougail/convert.py:1392
#: src/rougail/convert.py:668
msgid "variable \"{0}\" define multiple time"
msgstr ""
#: src/rougail/convert.py:766
msgid "params must be a dict for {0}"
msgstr ""
#: src/rougail/convert.py:787
msgid "\"{0}\" has an invalid \"params\" for {1}: {2}"
msgstr ""
#: src/rougail/convert.py:801
msgid "secret_manager must be a dict for {0}"
msgstr ""
#: src/rougail/convert.py:1129
msgid "Cannot execute annotate multiple time"
msgstr ""
#: src/rougail/error.py:67
#: src/rougail/convert.py:1136
msgid "invalid \"structural\" definition ({0}), we cannot load any structural file!"
msgstr ""
#: src/rougail/error.py:67 src/rougail/tiramisu.py:60
#: src/rougail/user_datas.py:354 src/rougail/user_datas.py:357
msgid "{0} in {1}"
msgstr ""
#: src/rougail/structural_commandline/annotator.py:67
#: src/rougail/object_model.py:50
msgid "unknown boolean value \"{0}\""
msgstr ""
#: src/rougail/object_model.py:187
msgid "cannot find variable \"{0}\" defined in attribute \"{1}\" for \"{2}\""
msgstr ""
#: src/rougail/object_model.py:193
msgid "the variable \"{0}\" is in fact a family in attribute \"{1}\" for \"{2}\""
msgstr ""
#: src/rougail/object_model.py:198 src/rougail/object_model.py:525
msgid "unknown object \"{0}\" in attribute \"{1}\" for \"{2}\""
msgstr ""
#: src/rougail/object_model.py:217
msgid "identifier parameter for \"{0}\" in \"{1}\" cannot be set none dynamic family"
msgstr ""
#: src/rougail/object_model.py:246
msgid "cannot find variable \"{0}\" defined in \"{1}\" for \"{2}\""
msgstr ""
#: src/rougail/object_model.py:251
msgid "variable \"{0}\" defined in \"{1}\" for \"{2}\" is a dynamic variable"
msgstr ""
#: src/rougail/object_model.py:268
msgid "the variable \"{0}\" is not a follower, so cannot have index type for param in \"{1}\""
msgstr ""
#: src/rougail/object_model.py:520
msgid "a variable \"{0}\" is needs in attribute \"{1}\" for \"{2}\" but it's a family"
msgstr ""
#: src/rougail/object_model.py:544
msgid "variable \"{0}\" has an attribute \"{1}\" calculated with the unknown variable \"{2}\""
msgstr ""
#: src/rougail/object_model.py:593
msgid "the variable \"{0}\" has an invalid \"{1}\" the variable \"{2}\" is in a sub dynamic option"
msgstr ""
#: src/rougail/object_model.py:602
msgid "the leader \"{0}\" has an invalid \"{1}\" the follower \"{2}\" is a multi"
msgstr ""
#: src/rougail/object_model.py:644
msgid "the variable \"{0}\" has an invalid attribute \"{1}\", the variable \"{2}\" must not be multi"
msgstr ""
#: src/rougail/object_model.py:658
msgid "the variable \"{0}\" has an invalid attribute \"{1}\", the variable must not be a multi or the variable \"{2}\" must be multi"
msgstr ""
#: src/rougail/object_model.py:672
msgid "the variable \"{0}\" has an invalid attribute \"{1}\", the variable must be a multi or the variable \"{2}\" must not be multi"
msgstr ""
#: src/rougail/object_model.py:686
msgid "the variable \"{0}\" has an invalid attribute \"{1}\", the variable \"{2}\" is multi but is inside a list"
msgstr ""
#: src/rougail/object_model.py:704
msgid "\"{0}\" attribut shall not have an \"optional\" attribute for variable \"{1}\""
msgstr ""
#: src/rougail/object_model.py:724
msgid "variable \"{0}\" has a default value calculated with \"{1}\" which has incompatible type"
msgstr ""
#: src/rougail/object_model.py:770
msgid "\"when\" is not allowed in format version 1.0 for attribute \"{0}\" for variable \"{1}\""
msgstr ""
#: src/rougail/object_model.py:775
msgid "the variable \"{0}\" has an invalid attribute \"{1}\", \"when\" and \"when_not\" cannot set together"
msgstr ""
#: src/rougail/object_model.py:783
msgid "\"when_not\" is not allowed in format version 1.0 for attribute \"{0}\" for variable \"{1}\""
msgstr ""
#: src/rougail/object_model.py:840
msgid "cannot find variable \"{0}\" for the information \"{1}\" when calculating \"{2}\""
msgstr ""
#: src/rougail/object_model.py:845
msgid "identifier not allowed for the information \"{0}\" when calculating \"{1}\""
msgstr ""
#: src/rougail/object_model.py:898
msgid "\"when\" is not allowed in format version 1.0 for attribute \"{0}\""
msgstr ""
#: src/rougail/object_model.py:904 src/rougail/object_model.py:914
msgid "the identifier has an invalid attribute \"{0}\", \"when\" and \"when_not\" cannot set together"
msgstr ""
#: src/rougail/object_model.py:939
msgid "the variable \"{0}\" is not a follower, so cannot have index type for \"{1}\""
msgstr ""
#: src/rougail/path.py:207
msgid "A variable or a family located in the \"{0}\" namespace shall not be used in the \"{1}\" namespace"
msgstr ""
#: src/rougail/structural_commandline/annotator.py:66
msgid "alternative_name \"{0}\" conflict with \"--help\""
msgstr ""
#: src/rougail/structural_commandline/annotator.py:72
#: src/rougail/structural_commandline/annotator.py:71
msgid "conflict alternative_name \"{0}\": \"{1}\" and \"{2}\""
msgstr ""
#: src/rougail/structural_commandline/annotator.py:95
msgid "negative_description is mandatory for boolean variable, but \"{0}\" hasn't"
#: src/rougail/structural_directory/__init__.py:127
msgid "duplicate dictionary file name {0}"
msgstr ""
#: src/rougail/structural_commandline/annotator.py:104
msgid "negative_description is only available for boolean variable, but \"{0}\" is \"{1}\""
#: src/rougail/structural_directory/config.py:32
msgid "Directories where dictionary files are placed"
msgstr ""
#: src/rougail/update/update.py:738
msgid "not a XML file: {0}"
#: src/rougail/structural_directory/config.py:49
msgid "Sort dictionaries from differents directories"
msgstr ""
#: src/rougail/structural_directory/config.py:58
msgid "Main namespace name"
msgstr ""
#: src/rougail/structural_directory/config.py:69
msgid "Extra namespaces"
msgstr ""
#: src/rougail/structural_directory/config.py:78
msgid "Extra namespace name"
msgstr ""
#: src/rougail/structural_directory/config.py:84
msgid "Directories where extra dictionary files are placed"
msgstr ""
#: src/rougail/tiramisu.py:187
msgid "cannot calculating \"{0}\" attribute for variable \"{1}\" in {2} with parameters \"{3}\": {4}"
msgstr ""
#: src/rougail/tiramisureflector.py:363
msgid "internal error, {0} is not a dynamic variable"
msgstr ""
#: src/rougail/user_datas.py:118
msgid "cannot load variable path \"{0}\", the identifier \"{1}\" is not valid in {2}"
msgstr ""
#: src/rougail/user_datas.py:196
msgid "the variable \"{0}\" contains secrets and should not be defined in {1}"
msgstr ""
#: src/rougail/user_datas.py:218
msgid "loaded from {0}"
msgstr ""
#: src/rougail/user_datas.py:258
msgid "cannot set the value \"{0}\" to the family {1}, it will be ignored when loading from {2}"
msgstr ""
#: src/rougail/user_datas.py:270
msgid "variable or family \"{0}\" does not exist, it will be ignored when loading from {1}"
msgstr ""
#: src/rougail/user_datas.py:276
msgid "\"{0}\" is the name of a dynamic family, it will be ignored when loading from {1}"
msgstr ""
#: src/rougail/user_datas.py:282
msgid "{0} loaded from {1}"
msgstr ""
#: src/rougail/user_datas.py:311
msgid "variable {0} at index \"{1}\" is {2}, it will be ignored when loading from {3}"
msgstr ""
#: src/rougail/user_datas.py:322
msgid "family {0} is {1}, {2} at index \"{3}\" will be ignored when loading from {4}"
msgstr ""
#: src/rougail/user_datas.py:335
msgid "variable {0} is {1}, it will be ignored when loading from {2}"
msgstr ""
#: src/rougail/user_datas.py:343
msgid "family {0} is {1}, {2} will be ignored when loading from {3}"
msgstr ""
#: src/rougail/user_datas.py:362
msgid "the value \"{0}\" is invalid for {1} at index \"{2}\", {3}, it will be ignored when loading from {4}"
msgstr ""
#: src/rougail/user_datas.py:373
msgid "the value \"{0}\" is invalid for {1}, {2}, it will be ignored when loading from {3}"
msgstr ""
#: src/rougail/utils.py:55
msgid "invalid variable or family name \"{0}\" must only contains lowercase ascii character, number or _"
msgstr ""
#: src/rougail/utils.py:117
#: src/rougail/utils.py:113
msgid "error in jinja \"{0}\" for the variable \"{1}\": {2}"
msgstr ""

View file

@ -1,50 +1,18 @@
[build-system]
build-backend = "flit_core.buildapi"
requires = ["flit_core >=3.8.0,<4"]
[project]
name = "rougail"
version = "1.1.1"
authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
readme = "README.md"
description = "A consistency handling system that was initially designed in the configuration management"
requires-python = ">=3.8"
classifiers = [
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"Programming Language :: Python",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
"Natural Language :: English",
"Natural Language :: French",
]
dependencies = [
"ruamel.yaml ~= 0.18.6",
"pydantic ~= 2.9.2",
"jinja2 ~= 3.1.4",
"tiramisu >=5.0,<6"
]
[project.optional-dependencies]
dev = [
"pylint ~= 3.0.3",
"pytest ~= 8.2.2",
"lxml ~= 5.2.2"
]
[project.urls]
Home = "https://forge.cloud.silique.fr/stove/rougail"
version = "1.2.0a31"
[tool.commitizen]
name = "cz_conventional_commits"
tag_format = "$version"
version_scheme = "pep440"
version_provider = "pep621"
version_files = [
"src/rougail/__version__.py",
"rougail-pyproject.toml:version",
"rougail-pyproject.toml:rougail-base == ",
"rougail-base-pyproject.toml:version",
]
update_changelog_on_bump = true
changelog_merge_prerelease = true

View file

@ -0,0 +1,52 @@
[build-system]
build-backend = "flit_core.buildapi"
requires = ["flit_core >=3.8.0,<4"]
[project]
name = "rougail-base"
version = "1.2.0a31"
authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
readme = "README.md"
description = "A consistency handling system that was initially designed in the configuration management"
requires-python = ">=3.10"
license = {file = "LICENSE"}
classifiers = [
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"Programming Language :: Python",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
"Natural Language :: English",
"Natural Language :: French",
]
dependencies = [
"jinja2 ~= 3.1.4",
"tiramisu ~= 5.0"
]
[project.optional-dependencies]
dev = [
"pylint ~= 3.0.3",
"pytest ~= 8.2.2",
]
[tool.flit.module]
name = "rougail"
[tool.flit.sdist]
exclude = [
"src/rougail/annotator",
"src/rougail/config",
"src/rougail/convert",
"src/rougail/structural_commandline",
"src/rougail/structural_directory",
"src/rougail/update",
]
[project.urls]
Home = "https://forge.cloud.silique.fr/stove/rougail"

35
rougail-pyproject.toml Normal file
View file

@ -0,0 +1,35 @@
[build-system]
build-backend = "flit_core.buildapi"
requires = ["flit_core >=3.8.0,<4"]
[project]
name = "rougail"
version = "1.2.0a31"
authors = [{name = "Emmanuel Garette", email = "gnunux@gnunux.info"}]
description = "A consistency handling system that was initially designed in the configuration management"
classifiers = [
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"Programming Language :: Python",
"Operating System :: OS Independent",
"Natural Language :: English",
"Natural Language :: French",
]
dependencies = [
"ruamel.yaml ~= 0.18.6",
"pydantic ~= 2.9.2",
"rougail-base == 1.2.0a31",
]
[tool.flit.sdist]
exclude = [
"src/rougail/error.py",
"src/rougail/i18n.py",
"src/rougail/tiramisu.py",
"src/rougail/user_datas.py",
"src/rougail/utils.py",
"src/rougail/__version__.py",
]
[project.urls]
Home = "https://forge.cloud.silique.fr/stove/rougail"

View file

@ -9,7 +9,7 @@ Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -25,302 +25,12 @@ You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from tiramisu import Config, undefined
from tiramisu.error import PropertiesOptionError, LeadershipError, ConfigError
from warnings import warn
from typing import List
from re import compile, findall
from .__version__ import __version__
from .convert import RougailConvert
from .config import RougailConfig
from .update import RougailUpgrade
from .object_model import CONVERT_OPTION
from .utils import normalize_family
try:
from .convert import Rougail
from .config import RougailConfig
def tiramisu_display_name(
kls,
subconfig,
with_quote: bool = False,
) -> str:
"""Replace the Tiramisu display_name function to display path + description"""
doc = kls._get_information(subconfig, "doc", None)
comment = f" ({doc})" if doc and doc != kls.impl_getname() else ""
if "{{ identifier }}" in comment:
comment = comment.replace("{{ identifier }}", str(subconfig.identifiers[-1]))
path = kls.impl_getpath()
if "{{ identifier }}" in path and subconfig.identifiers:
path = path.replace(
"{{ identifier }}", normalize_family(str(subconfig.identifiers[-1]))
)
if with_quote:
return f'"{path}"{comment}'
return f"{path}{comment}"
class Rougail:
"""Main Rougail object"""
def __init__(
self,
rougailconfig=None,
) -> None:
if rougailconfig is None:
rougailconfig = RougailConfig
self.rougailconfig = rougailconfig
self.converted = RougailConvert(self.rougailconfig)
self.config = None
def add_path_prefix(
self,
path_prefix: str,
) -> None:
"""Add a prefix"""
self.converted.load_config()
self.converted.parse_directories(path_prefix)
def run(self):
"""Get Tiramisu Config"""
if not self.config:
tiram_obj = self.converted.save(self.rougailconfig["tiramisu_cache"])
optiondescription = {}
custom_types = {
custom.__name__: custom
for custom in self.rougailconfig["custom_types"].values()
}
exec(tiram_obj, custom_types, optiondescription) # pylint: disable=W0122
self.config = Config(
optiondescription["option_0"],
display_name=tiramisu_display_name,
)
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()
def user_datas(self, user_datas: List[dict]):
values = {}
errors = []
warnings = []
for datas in user_datas:
options = datas.get("options", {})
for name, data in datas.get("values", {}).items():
values[name] = {
"values": data,
"options": options.copy(),
}
errors.extend(datas.get("errors", []))
warnings.extend(datas.get("warnings", []))
self._auto_configure_dynamics(values)
while values:
value_is_set = False
for option in self._get_variable(self.config):
path = option.path()
if path not in values:
path = path.upper()
options = values.get(path, {}).get("options", {})
if path not in values or options.get("upper") is not True:
continue
else:
options = values[path].get("options", {})
value = values[path]["values"]
if option.ismulti():
if options.get("multi_separator") and not isinstance(value, list):
value = value.split(options["multi_separator"])
values[path]["values"] = value
if options.get("needs_convert"):
value = [convert_value(option, val) for val in value]
values[path]["values"] = value
values[path]["options"]["needs_convert"] = False
elif options.get("needs_convert"):
value = convert_value(option, value)
index = option.index()
if index is not None:
if not isinstance(value, list) or index >= len(value):
continue
value = value[index]
try:
option.value.set(value)
value_is_set = True
if index is not None:
values[path]["values"][index] = undefined
if set(values[path]["values"]) == {undefined}:
values.pop(path)
else:
values.pop(path)
except Exception as err:
if path != option.path():
values[option.path()] = values.pop(path)
if not value_is_set:
break
for path, data in values.items():
try:
option = self.config.option(path)
value = data["values"]
if option.isfollower():
for index, val in enumerate(value):
if val is undefined:
continue
self.config.option(path, index).value.set(val)
else:
option.value.set(value)
except AttributeError as err:
errors.append(str(err))
except (ValueError, LeadershipError) as err:
# errors.append(str(err).replace('"', "'"))
errors.append(str(err))
except PropertiesOptionError as err:
# warnings.append(f'"{err}" but is defined in "{self.filename}"')
warnings.append(str(err))
return {
"errors": errors,
"warnings": warnings,
}
def _get_variable(self, config):
for subconfig in config:
if subconfig.isoptiondescription():
yield from self._get_variable(subconfig)
else:
yield subconfig
def _auto_configure_dynamics(
self,
values,
):
cache = {}
added = []
for path, data in list(values.items()):
value = data["values"]
# for value in data['values'].items():
try:
option = self.config.option(path)
option.name()
except (ConfigError, PropertiesOptionError):
pass
except AttributeError:
config = self.config
current_path = ""
identifiers = []
for name in path.split(".")[:-1]:
if current_path:
current_path += "."
current_path += name
if current_path in cache:
config, identifier = cache[current_path]
identifiers.append(identifier)
else:
tconfig = config.option(name)
try:
tconfig.group_type()
config = tconfig
except AttributeError:
for tconfig in config.list(uncalculated=True):
if tconfig.isdynamic(only_self=True):
identifier = self._get_identifier(
tconfig.name(), name
)
if identifier is None:
continue
dynamic_variable = tconfig.information.get(
"dynamic_variable",
None,
)
if not dynamic_variable:
continue
option_type = self.config.option(
dynamic_variable
).information.get("type")
if identifiers:
for s in identifiers:
dynamic_variable = dynamic_variable.replace(
"{{ identifier }}", str(s), 1
)
if dynamic_variable not in values:
values[dynamic_variable] = {"values": []}
added.append(dynamic_variable)
elif dynamic_variable not in added:
continue
config = tconfig
# option_type = option.information.get('type')
typ = CONVERT_OPTION.get(option_type, {}).get(
"func"
)
if typ:
identifier = typ(identifier)
if (
identifier
not in values[dynamic_variable]["values"]
):
values[dynamic_variable]["values"].append(
identifier
)
identifiers.append(identifier)
cache[current_path] = config, identifier
break
else:
if option.isdynamic():
parent_option = self.config.option(path.rsplit(".", 1)[0])
identifiers = self._get_identifier(
parent_option.name(uncalculated=True),
parent_option.name(),
)
dynamic_variable = None
while True:
dynamic_variable = parent_option.information.get(
"dynamic_variable",
None,
)
if dynamic_variable:
break
parent_option = self.config.option(
parent_option.path().rsplit(".", 1)[0]
)
if "." not in parent_option.path():
parent_option = None
break
if not parent_option:
continue
identifiers = parent_option.identifiers()
for identifier in identifiers:
dynamic_variable = dynamic_variable.replace(
"{{ identifier }}", str(identifier), 1
)
if dynamic_variable not in values:
values[dynamic_variable] = {"values": []}
added.append(dynamic_variable)
elif dynamic_variable not in added:
continue
option_type = option.information.get("type")
typ = CONVERT_OPTION.get(option_type, {}).get("func")
if typ:
identifier = typ(identifier)
if identifier not in values[dynamic_variable]["values"]:
values[dynamic_variable]["values"].append(identifier)
cache[option.path()] = option, identifier
def _get_identifier(self, true_name, name) -> str:
regexp = true_name.replace("{{ identifier }}", "(.*)")
finded = findall(regexp, name)
if len(finded) != 1 or not finded[0]:
return
return finded[0]
def convert_value(option, value):
if value == "":
return None
option_type = option.information.get("type")
func = CONVERT_OPTION.get(option_type, {}).get("func")
if func:
return func(value)
return value
__all__ = ("Rougail", "RougailConfig", "RougailUpgrade")
__all__ = ("Rougail", "RougailConfig", "__version__")
except ModuleNotFoundError as err:
__all__ = ("__version__",)

View file

@ -0,0 +1 @@
__version__ = "1.2.0a31"

View file

@ -1,4 +1,4 @@
"""Annotate dictionaries
"""Annotate structural file
Created by:
EOLE (http://eole.orion.education.fr)
@ -9,7 +9,7 @@ Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -71,16 +71,38 @@ class SpaceAnnotator: # pylint: disable=R0903
if extra_annotator in ANNOTATORS:
continue
get_annotators(ANNOTATORS, extra_annotator)
for plugin in objectspace.plugins:
for structural in objectspace.structurals:
try:
get_annotators(ANNOTATORS, f"rougail.{plugin}", "annotator")
get_annotators(
ANNOTATORS, f"rougail.structural_{structural}", "annotator"
)
except ModuleNotFoundError:
pass
for user_data in objectspace.user_datas:
try:
get_annotators(
ANNOTATORS, f"rougail.user_data_{user_data}", "annotator"
)
except ModuleNotFoundError:
pass
if objectspace.output:
try:
get_annotators(
ANNOTATORS, f"rougail.output_{objectspace.output}", "annotator"
)
except ModuleNotFoundError:
pass
annotators = ANNOTATORS["rougail.annotator"].copy()
for extra_annotator in objectspace.extra_annotators:
annotators.extend(ANNOTATORS[extra_annotator])
for plugin in objectspace.plugins:
annotators.extend(ANNOTATORS[f"rougail.{plugin}.annotator"])
for structural in objectspace.structurals:
annotators.extend(ANNOTATORS[f"rougail.structural_{structural}.annotator"])
for user_data in objectspace.user_datas:
annotators.extend(ANNOTATORS[f"rougail.user_data_{user_data}.annotator"])
if objectspace.output:
annotators.extend(
ANNOTATORS[f"rougail.output_{objectspace.output}.annotator"]
)
annotators = sorted(annotators, key=get_level)
functions = {}
functions_files = objectspace.functions_files

View file

@ -9,7 +9,7 @@ Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -29,7 +29,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
from rougail.convert.object_model import VariableCalculation
class Mode: # pylint: disable=R0903
@ -125,11 +125,22 @@ class Annotator(Walk):
if family.type == "dynamic" and isinstance(
family.dynamic, VariableCalculation
):
path = self.objectspace.paths.get_full_path(
family.dynamic.variable,
family.path,
path = family.dynamic.variable
if (
family.version != "1.0"
and self.objectspace.paths.regexp_relative.search(path)
):
path = self.objectspace.paths.get_full_path(
family.dynamic.variable,
family.path,
)
if family.version == "1.0" and "{{ suffix }}" in path:
path = path.replace("{{ suffix }}", "{{ identifier }}")
self.objectspace.dynamics_variable.setdefault(path, []).append(
family.path
)
self.objectspace.informations.add(family.path, "dynamic_variable", path)
self.objectspace.informations.add(family.path, "ymlfiles", family.xmlfiles)
def change_modes(self):
"""change the mode of variables"""
@ -333,7 +344,7 @@ class Annotator(Walk):
if self._has_mode(variable):
msg = _(
'the variable "{0}" is in "{1}" mode but family has the higher family mode "{2}"'
).format(variable.name, variable_mode, family_mode)
).format(variable.path, variable_mode, family_mode)
raise DictConsistencyError(msg, 61, variable.xmlfiles)
self._set_auto_mode(variable, family_mode)
if not variable.mode:

View file

@ -9,7 +9,7 @@ Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -29,7 +29,7 @@ from typing import Union
from rougail.i18n import _
from rougail.error import DictConsistencyError
from rougail.annotator.variable import Walk
from rougail.object_model import Calculation
from rougail.convert.object_model import Calculation
PROPERTIES = (
@ -120,7 +120,7 @@ class Annotator(Walk):
else:
value = []
for calculation in frozen:
calculation_copy = calculation.copy()
calculation_copy = calculation.model_copy()
calculation_copy.attribute_name = "frozen"
calculation_copy.ori_path = calculation_copy.path
calculation_copy.path = path

View file

@ -9,7 +9,7 @@ Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -29,7 +29,7 @@ from rougail.annotator.variable import Walk
from rougail.i18n import _
from rougail.error import DictConsistencyError
from rougail.object_model import Calculation
from rougail.convert.object_model import Calculation
class Annotator(Walk): # pylint: disable=R0903
@ -47,6 +47,7 @@ class Annotator(Walk): # pylint: disable=R0903
self.objectspace = objectspace
self.convert_value()
self.valid_choices()
self.valid_regexp()
def convert_value(self) -> None:
"""convert value"""
@ -75,8 +76,8 @@ class Annotator(Walk): # pylint: disable=R0903
raise DictConsistencyError(msg, 68, variable.xmlfiles)
if variable.path in self.objectspace.followers and multi != "submulti":
msg = _(
'the follower "{0}" without multi attribute can only have one value'
).format(variable.name)
'the follower "{0}" is not multi, so cannot have a list has default value'
).format(variable.path)
raise DictConsistencyError(msg, 87, variable.xmlfiles)
if not variable.default:
variable.default = None
@ -92,7 +93,7 @@ class Annotator(Walk): # pylint: disable=R0903
elif variable.multi:
msg = _(
'the variable "{0}" is multi but has a non list default value'
).format(variable.name)
).format(variable.path)
raise DictConsistencyError(msg, 12, variable.xmlfiles)
elif variable.path in self.objectspace.followers:
self.objectspace.default_multi[variable.path] = variable.default
@ -114,7 +115,9 @@ class Annotator(Walk): # pylint: disable=R0903
if isinstance(variable.choices, Calculation):
continue
if variable.choices is None:
msg = f'the variable "{variable.path}" is a "choice" variable but don\'t have any choice'
msg = _(
'the variable "{0}" is a "choice" variable but don\'t have any choice'
).format(variable.path)
raise DictConsistencyError(msg, 19, variable.xmlfiles)
if not variable.mandatory and not variable.multi:
self.add_choice_nil(variable)
@ -125,3 +128,13 @@ class Annotator(Walk): # pylint: disable=R0903
if choice is None:
return
variable.choices.append(None)
def valid_regexp(self) -> None:
for variable in self.get_variables():
if variable.type != "regexp":
continue
if variable.regexp is None:
msg = _(
'the variable "{0}" is a "regexp" variable but don\'t have any regexp'
).format(variable.path)
raise DictConsistencyError(msg, 66, variable.xmlfiles)

View file

@ -9,7 +9,7 @@ Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -25,10 +25,11 @@ You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from rougail.i18n import _
from rougail.error import DictConsistencyError
from rougail.object_model import Calculation, VariableCalculation
from tiramisu.error import display_list
from rougail.i18n import _
from rougail.utils import calc_multi_for_type_variable
from rougail.error import DictConsistencyError
from rougail.convert.object_model import Calculation, VariableCalculation
class Walk:
@ -62,12 +63,6 @@ class Annotator(Walk): # pylint: disable=R0903
if not objectspace.paths:
return
self.objectspace = objectspace
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",
@ -75,29 +70,60 @@ class Annotator(Walk): # pylint: disable=R0903
bool: "boolean",
float: "float",
}
self.verify_secret_managers()
self.verify_choices()
self.convert_variable()
self.convert_test()
self.convert_examples()
self.convert_help()
def verify_secret_managers(self):
for variable in self.get_variables():
if not variable.secret_manager:
continue
path = variable.path
if variable.type not in ["unix_user", "secret"]:
msg = _(
'only "unix_user" or "secret" variable type can have "secret_manager" attribute, but "{0}" has type "{1}"'
)
raise DictConsistencyError(
msg.format(path, variable.type), 56, variable.xmlfiles
)
if variable.multi and path not in self.objectspace.leaders:
msg = _(
'the variable "{0}" has attribute "secret_manager" but is a multi variable'
)
raise DictConsistencyError(msg.format(path), 57, variable.xmlfiles)
if variable.default is not None:
msg = _(
'the variable "{0}" has attribute "secret_manager" so must not have default value'
)
raise DictConsistencyError(msg.format(path), 59, variable.xmlfiles)
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_inference(variable)
self._convert_variable_multi(variable)
for variable in self.get_variables():
if variable.type == "symlink":
continue
self.objectspace.informations.add(
variable.path, "ymlfiles", variable.xmlfiles
)
if variable.version != "1.0" and isinstance(
variable.default, VariableCalculation
):
calculated_variable_path, calculated_variable, identifier = (
variable.default.get_variable(self.objectspace)
)
else:
calculated_variable = None
if variable.version != "1.0" and calculated_variable is not None:
self._default_variable_copy_informations(variable, calculated_variable)
self._convert_variable(variable)
def _convert_variable_inference(
@ -105,62 +131,83 @@ class Annotator(Walk): # pylint: disable=R0903
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
if variable.type is not None:
return
# 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:
variable.multi = isinstance(variable.default, list)
tested_value = variable.default
variable.type = self.basic_types.get(type(tested_value), None)
def _convert_variable_multi(
self,
variable,
) -> None:
# variable has no multi attribute
if variable.multi is not None:
return
if variable.path in self.objectspace.leaders:
variable.multi = self.objectspace.multis[variable.path] = True
elif variable.version != "1.0" and isinstance(
variable.default, VariableCalculation
):
calculated_variable_path, calculated_variable, identifier = (
variable.default.get_variable(self.objectspace)
)
if calculated_variable is not None:
if calculated_variable.multi is None:
if (
isinstance(calculated_variable.default, VariableCalculation)
and variable.path == calculated_variable.default.path
):
msg = _(
'the "{0}" default value is a calculation with itself'.format(
variable.path
)
)
raise DictConsistencyError(msg, 75, variable.xmlfiles)
self._convert_variable_multi(calculated_variable)
variable.multi = calc_multi_for_type_variable(
variable,
calculated_variable_path,
calculated_variable,
self.objectspace,
)[1]
if (
calculated_variable.path in self.objectspace.followers
and variable.mandatory is calculated_variable.mandatory is False
and calculated_variable.path.rsplit(".", 1)[0]
!= variable.path.rsplit(".", 1)[0]
):
variable.empty = False
else:
variable.multi = isinstance(variable.default, list)
def _default_variable_copy_informations(
self,
variable,
calculated_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
if (
not isinstance(variable.default, VariableCalculation)
or variable.type is not None
):
return
# copy type and params
calculated_variable_path = variable.default.variable
calculated_variable, identifier = 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
if variable.type == "choice" and variable.choices is None:
variable.choices = calculated_variable.choices
if variable.type == "regexp" and variable.regexp is None:
variable.regexp = calculated_variable.regexp
def _convert_variable(
self,
@ -169,6 +216,9 @@ class Annotator(Walk): # pylint: disable=R0903
# variable without description: description is the name
if not variable.description:
variable.description = variable.name
if variable.type is None:
variable.type = "string"
self.objectspace.informations.add(variable.path, "type", variable.type)
if variable.path in self.objectspace.followers:
if not variable.multi:
self.objectspace.multis[variable.path] = True
@ -177,8 +227,6 @@ class Annotator(Walk): # pylint: disable=R0903
elif variable.multi:
self.objectspace.multis[variable.path] = True
if variable.path in self.objectspace.leaders:
if not self.objectspace.multis.get(variable.path, False):
variable.multi = self.objectspace.multis[variable.path] = True
family = self.objectspace.paths[variable.path.rsplit(".", 1)[0]]
if variable.hidden:
family.hidden = variable.hidden

View file

@ -10,7 +10,7 @@ Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -29,12 +29,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from pathlib import Path
from tiramisu import Config
from ruamel.yaml import YAML
from .utils import _, load_modules, normalize_family
from .convert import RougailConvert
from ..utils import _, load_modules
from ..tiramisu import normalize_family
from ..convert import RougailConvert
from ..convert.object_model import get_convert_option_types
RENAMED = {
"dictionaries_dir": "main_dictionaries",
"dictionaries_dir": "main_structural_directories",
"main_dictionaries": "main_structural_directories",
"variable_namespace": "main_namespace",
"functions_file": "functions_files",
}
@ -48,7 +51,7 @@ def get_sub_modules():
global SUBMODULES
if SUBMODULES is None:
SUBMODULES = {}
for submodule in Path(__file__).parent.iterdir():
for submodule in Path(__file__).parent.parent.iterdir():
if submodule.name.startswith("_") or not submodule.is_dir():
continue
config_file = submodule / "config.py"
@ -60,43 +63,63 @@ def get_sub_modules():
def get_level(module):
return module["level"]
return float(module["level"]) + {
"structural": 0.1,
"user data": 0.2,
"output": 0.3,
}.get(module["process"])
class _RougailConfig:
def __init__(self, backward_compatibility: bool, root, extra_vars: dict):
def __init__(self, backward_compatibility: bool, add_extra_options: bool):
self.backward_compatibility = backward_compatibility
self.add_extra_options = add_extra_options
self.root = None
def copy(self):
rougailconfig = _RougailConfig(
self.backward_compatibility,
self.add_extra_options,
)
if self.root:
rougailconfig.config.value.importation(self.config.value.exportation())
rougailconfig.config.property.importation(
self.config.property.exportation()
)
rougailconfig.config.property.read_only()
rougailconfig.root = self.root
rougailconfig.config = self.config
rougailconfig.extra_vars = self.extra_vars
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 generate_config(self):
root, extra_vars = _rougail_config(
self.backward_compatibility, self.add_extra_options
)
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
self.config.property.read_only()
def __setitem__(
self,
key,
value,
) -> None:
if self.root is None:
self.generate_config()
if key in self.not_in_tiramisu:
setattr(self, key, value)
else:
@ -122,6 +145,8 @@ class _RougailConfig:
self,
key,
) -> None:
if self.root is None:
self.generate_config()
if key in self.not_in_tiramisu:
return getattr(self, key)
if key == "export_with_import":
@ -154,6 +179,8 @@ class _RougailConfig:
yield f"{option.path()}: {option.value.get()}"
def __repr__(self):
if self.root is None:
self.generate_config()
self.config.property.read_write()
try:
values = "\n".join(self.parse(self.config))
@ -172,7 +199,7 @@ class FakeRougailConvert(RougailConvert):
super().__init__({})
def load_config(self) -> None:
self.sort_dictionaries_all = False
self.sort_structural_files_all = False
self.main_namespace = None
self.suffix = ""
self.custom_types = {}
@ -182,103 +209,29 @@ class FakeRougailConvert(RougailConvert):
self.base_option_name = "baseoption"
self.export_with_import = True
self.internal_functions = []
self.plugins = ["structural_commandline"]
self.force_optional = False
self.structurals = ["commandline"]
self.user_datas = []
self.output = None
self.add_extra_options = self.add_extra_options
self.tiramisu_cache = False
self.load_unexist_redefine = False
def get_rougail_config(
*,
def _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 = f"""default_dictionary_format_version:
description: Dictionary format version by default, if not specified in dictionary file
) -> "OptionDescription":
rougail_options = f"""default_structural_format_version:
description: {_('Structure format version by default, if not specified in structure 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_namespace_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
description: {_("File with functions")}
alternative_name: c
type: unix_filename
params:
@ -290,74 +243,73 @@ functions_files:
mandatory: false
modes_level:
description: All modes level available
description: {_("All modes level available")}
multi: true
mandatory: false
"""
if backward_compatibility:
rougail_options += """
default:
rougail_options += """ default:
- basic
- standard
- advanced
"""
rougail_options += """
rougail_options += f"""
default_family_mode:
description: Default mode for a family
description: {_("Default mode for a family")}
default:
jinja: |
{% if modes_level %}
{{ modes_level[0] }}
{% endif %}
{{% if modes_level %}}
{{{{ modes_level[0] }}}}
{{% endif %}}
disabled:
jinja: |
{% if not modes_level %}
{{% if not modes_level %}}
No mode
{% endif %}
{{% endif %}}
validators:
- type: jinja
jinja: |
{% if default_family_mode not in modes_level %}
not in modes_level ({modes_level})
{% endif %}
{{% 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
description: {_("Default mode for a variable")}
default:
jinja: |
{% if modes_level %}
{% if modes_level | length == 1 %}
{{ modes_level[0] }}
{% else %}
{{ modes_level[1] }}
{% endif %}
{% endif %}
{{% if modes_level %}}
{{% if modes_level | length == 1 %}}
{{{{ modes_level[0] }}}}
{{% else %}}
{{{{ modes_level[1] }}}}
{{% endif %}}
{{% endif %}}
disabled:
jinja: |
{% if not modes_level %}
{{% if not modes_level %}}
No mode
{% endif %}
{{% endif %}}
validators:
- type: jinja
jinja: |
{% if default_variable_mode not in modes_level %}
not in modes_level ({modes_level})
{% endif %}
{{% 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
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
description: {_("In cache file, do not importation of Tiramisu and other dependencies")}
default: false
commandline: false
tiramisu_cache:
description: Tiramisu cache filename
description: {_("Tiramisu cache filename")}
alternative_name: t
type: unix_filename
mandatory: false
@ -365,77 +317,119 @@ tiramisu_cache:
allow_relative: true
internal_functions:
description: Name of internal functions that we can use as a function
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
description: {_("Name of extra annotators")}
multi: true
mandatory: false
commandline: false
suffix:
description: Suffix add to generated option name
description: {_("Suffix add to generated options name")}
default: ''
mandatory: false
commandline: false
force_optional:
description: {_("Every variables in calculation are optionals")}
default: False
load_unexist_redefine:
description: {_("Loads redefine variables even if there don't already exists")}
commandline: false
default: False
secret_manager:
pattern:
description: {_("The secret pattern to build item name in Bitwarden")}
help: {_("The pattern is in Jinja format")}
default: "{{{{ project }}}} - {{{{ environment }}}} - {{{{ service }}}} - {{{{ user }}}}"
"""
processes = {
"structural": [],
"output": [],
"user data": [],
"output": [],
}
processes_empty = []
for module in get_sub_modules().values():
data = module.get_rougail_config()
processes[data["process"]].append(data)
data = module.get_rougail_config(backward_compatibility=backward_compatibility)
if data["process"]:
processes[data["process"]].append(data)
else:
processes_empty.append(data["options"])
# reorder
for process in processes:
processes[process] = list(sorted(processes[process], key=get_level))
rougail_process = """step: # Load and exporter steps
disabled:
variable: upgrade"""
rougail_process = "step: # Load and exporter steps"
for process in processes:
if processes[process]:
objects = processes[process]
rougail_process += """
{NAME}:
description: Select for {NAME}
process_name = normalize_family(process)
tr_process_name = _(process_name)
rougail_process += f"""
{process_name}:
description: {_('Select for {0}').format(tr_process_name)}
"""
if process != "structural":
rougail_process += """
alternative_name: {NAME[0]}
choices:
""".format(
NAME=normalize_family(process),
)
NAME=normalize_family(process),
)
rougail_process += """
choices:
"""
for obj in objects:
rougail_process += f" - {obj['name']}\n"
if process == "structural":
rougail_process += " commandline: false"
rougail_process += """ commandline: false
multi: true
default:
- directory
"""
hidden_outputs = [
process["name"]
for process in processes["output"]
if 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 is not propertyerror and _.output == 'NAME' %}
Cannot load structural for NAME output
{% endif %}
""".replace(
"NAME", hidden_output
)
elif process == "user data":
rougail_process += """ multi: true
mandatory: false
"""
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:
rougail_process += """
disabled:
type: jinja
jinja: |
"""
for hidden_output in hidden_outputs:
rougail_process += """ {% if _.output == 'NAME' %}
rougail_process += """ {% if _.output is not propertyerror and _.output == 'NAME' %}
Cannot load user data for NAME output
{% endif %}
""".replace(
{% endif %}""".replace(
"NAME", hidden_output
)
elif objects:
@ -443,10 +437,10 @@ suffix:
DEFAULT=objects[0]["name"]
)
else:
if process == 'output':
prop = 'hidden'
if process == "output":
prop = "hidden"
else:
prop = 'disabled'
prop = "disabled"
rougail_process += """
{NAME}:
description: Select for {NAME}
@ -460,9 +454,32 @@ suffix:
NAME=normalize_family(process),
PROP=prop,
)
rougail_process += f"""
define_default_params: false # {_('Override default parameters for option type')}
default_params:
description: {_("Default parameters for option type")}
disabled:
variable: _.define_default_params
when: false
"""
for typ, params in get_convert_option_types():
rougail_process += f"""
{typ}:
"""
for key, key_type, multi, value in params:
rougail_process += f"""
{key}:
type: {key_type}
multi: {multi}
mandatory: false
default: {value}
"""
# print(rougail_process)
rougail_options += rougail_process
convert = FakeRougailConvert(add_extra_options)
convert._init()
convert.init()
convert.namespace = None
convert.parse_root_file(
"rougail.config",
@ -470,27 +487,48 @@ suffix:
"1.1",
YAML().load(rougail_options),
)
for process_empty in processes_empty:
convert.parse_root_file(
"rougail.config",
"",
"1.1",
YAML().load(process_empty),
)
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
objects = []
for obj in sorted(
[obj for objects in processes.values() for obj in objects], key=get_level
):
if "extra_vars" in obj:
extra_vars |= obj["extra_vars"]
if not "options" in obj:
continue
if not isinstance(obj["options"], list):
options = [obj["options"]]
else:
options = obj["options"]
for option in options:
convert.parse_root_file(
f'rougail.config.{obj["name"]}',
"",
"1.1",
YAML().load(obj["options"]),
YAML().load(option),
)
tiram_obj = convert.save(None)
tiram_obj = convert.save()
optiondescription = {}
exec(tiram_obj, {}, optiondescription) # pylint: disable=W0122
return optiondescription["option_0"], extra_vars
def get_rougail_config(
*,
backward_compatibility: bool = True,
add_extra_options: bool = True,
) -> _RougailConfig:
return _RougailConfig(
backward_compatibility,
optiondescription["option_0"],
extra_vars=extra_vars,
add_extra_options,
)

View file

@ -0,0 +1,66 @@
"""Silique (https://www.silique.fr)
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from tiramisu import Config
from warnings import warn
from .convert import RougailConvert
from ..config import RougailConfig
from ..user_datas import UserDatas
from ..tiramisu import tiramisu_display_name
class Rougail(UserDatas):
"""Main Rougail object"""
def __init__(
self,
rougailconfig=None,
) -> None:
if rougailconfig is None:
rougailconfig = RougailConfig
self.rougailconfig = rougailconfig
self.converted = RougailConvert(self.rougailconfig)
self.config = None
def get_root_option(self):
tiram_obj = self.converted.save()
optiondescription = {}
custom_types = {
custom.__name__: custom
for custom in self.rougailconfig["custom_types"].values()
}
exec(tiram_obj, custom_types, optiondescription) # pylint: disable=W0122
return optiondescription["option_0"]
def run(self):
"""Get Tiramisu Config"""
if not self.config:
self.config = Config(
self.get_root_option(),
display_name=tiramisu_display_name,
)
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()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,991 @@
"""Rougail object model
Silique (https://www.silique.fr)
Copyright (C) 2023-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from typing import Optional, Union, get_type_hints, Any, Literal, List, Dict, Iterator
from pydantic import (
BaseModel,
StrictBool,
StrictInt,
StrictFloat,
StrictStr,
ConfigDict,
)
import tiramisu
from tiramisu.config import get_common_path
from ..utils import (
get_jinja_variable_to_param,
calc_multi_for_type_variable,
undefined,
PROPERTY_ATTRIBUTE,
)
from ..i18n import _
from ..error import DictConsistencyError, VariableCalculationDependencyError
from ..tiramisu import CONVERT_OPTION
BASETYPE = Union[StrictBool, StrictInt, StrictFloat, StrictStr, None]
def get_convert_option_types():
for typ, datas in CONVERT_OPTION.items():
obj = getattr(tiramisu, datas["opttype"])
initkwargs = datas.get("initkwargs", {})
if obj == tiramisu.SymLinkOption:
continue
if obj == tiramisu.ChoiceOption:
inst = obj("a", "a", ("a",), **initkwargs)
else:
inst = obj("a", "a", **initkwargs)
extra = getattr(inst, "_extra", {})
if not extra:
continue
params = []
for key, value in extra.items():
if key.startswith("_"):
continue
multi = False
if isinstance(value, bool):
key_type = "boolean"
elif isinstance(value, str):
key_type = "string"
elif isinstance(value, list):
key_type = "string"
multi = True
params.append((key, key_type, multi, value))
yield typ, params
class Param(BaseModel):
key: str
namespace: Optional[str]
model_config = ConfigDict(extra="forbid")
def __init__(
self,
path,
attribute,
family_is_dynamic,
xmlfiles,
**kwargs,
) -> None:
super().__init__(**kwargs)
def to_param(
self, attribute_name, objectspace, path, version, namespace, xmlfiles
) -> dict:
return self.model_dump()
class AnyParam(Param):
type: str
value: Union[BASETYPE, List[BASETYPE]]
class VariableParam(Param):
type: str
variable: str
propertyerror: bool = True
whole: bool = False
optional: bool = False
def to_param(
self, attribute_name, objectspace, path, version, namespace, xmlfiles
) -> dict:
param = super().to_param(
attribute_name, objectspace, path, version, namespace, xmlfiles
)
variable, identifier = objectspace.paths.get_with_dynamic(
param["variable"],
path,
version,
namespace,
xmlfiles,
)
if not variable:
if not param.get("optional"):
msg = _(
'cannot find variable "{0}" defined in attribute "{1}" for "{2}"'
).format(param["variable"], attribute_name, path)
raise DictConsistencyError(msg, 22, xmlfiles)
return None
if isinstance(variable, objectspace.family):
msg = _(
'the variable "{0}" is in fact a family in attribute "{1}" for "{2}"'
).format(variable["name"], attribute_name, path)
raise DictConsistencyError(msg, 42, xmlfiles)
if not isinstance(variable, objectspace.variable):
msg = _('unknown object "{0}" in attribute "{1}" for "{2}"').format(
variable, attribute_name, path
)
raise DictConsistencyError(msg, 44, xmlfiles)
param["variable"] = variable
if identifier:
param["identifier"] = identifier
return param
class IdentifierParam(Param):
type: str
identifier: Optional[int] = None
def __init__(
self,
**kwargs,
) -> None:
if not kwargs["family_is_dynamic"]:
msg = _(
'identifier parameter for "{0}" in "{1}" cannot be set none dynamic family'
).format(kwargs["attribute"], kwargs["path"])
raise DictConsistencyError(msg, 10, kwargs["xmlfiles"])
super().__init__(**kwargs)
class InformationParam(Param):
type: str
information: str
variable: Optional[str] = None
def to_param(
self, attribute_name, objectspace, path, version, namespace, xmlfiles
) -> dict:
param = super().to_param(
attribute_name, objectspace, path, version, namespace, xmlfiles
)
if not param["variable"]:
del param["variable"]
return param
variable, identifier = objectspace.paths.get_with_dynamic(
param["variable"],
path,
version,
namespace,
xmlfiles,
)
if not variable:
msg = _('cannot find variable "{0}" defined in "{1}" for "{2}"').format(
param["variable"], attribute_name, path
)
raise DictConsistencyError(msg, 14, xmlfiles)
if identifier:
msg = _(
'variable "{0}" defined in "{1}" for "{2}" is a dynamic variable'
).format(param["variable"], attribute_name, path)
raise DictConsistencyError(msg, 15, xmlfiles)
param["variable"] = variable
return param
class IndexParam(Param):
type: str
def to_param(
self, attribute_name, objectspace, path, version, namespace, xmlfiles
) -> dict:
if path not in objectspace.followers and (
attribute_name != "validators" or path not in objectspace.multis
):
msg = _(
'the variable "{0}" is not a follower, so cannot have index type for param in "{1}"'
).format(path, attribute)
raise DictConsistencyError(msg, 25, xmlfiles)
return super().to_param(
attribute_name, objectspace, path, version, namespace, xmlfiles
)
class NamespaceParam(Param):
type: str
namespace: str
def to_param(
self, attribute_name, objectspace, path, version, namespace, xmlfiles
) -> dict:
namespace = self.namespace
if namespace:
namespace = objectspace.paths[namespace].description
return {
"type": "any",
"value": namespace,
"key": self.key,
}
PARAM_TYPES = {
"any": AnyParam,
"variable": VariableParam,
"identifier": IdentifierParam,
"information": InformationParam,
"index": IndexParam,
"namespace": NamespaceParam,
}
class Calculation(BaseModel):
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")
def get_params(self, objectspace):
if not self.params:
return {}
if self.ori_path is None:
path = self.path
else:
path = self.ori_path
params = {}
for param_obj in self.params:
param = param_obj.to_param(
self.attribute_name,
objectspace,
path,
self.version,
self.namespace,
self.xmlfiles,
)
if param is None:
continue
params[param.pop("key")] = param
return params
class JinjaCalculation(Calculation):
attribute_name: Literal[
"frozen",
"hidden",
"mandatory",
"empty",
"disabled",
"default",
"validators",
"choices",
"dynamic",
"secret_manager",
]
jinja: StrictStr
params: Optional[List[Param]] = None
return_type: BASETYPE = None
description: Optional[StrictStr] = None
def _jinja_to_function(
self,
function,
return_type,
multi,
objectspace,
*,
add_help=False,
params: Optional[dict] = None,
):
variable = objectspace.paths[self.path]
jinja_path = f"{self.attribute_name}_{self.path}"
idx = 0
while jinja_path in objectspace.jinja:
jinja_path = f"{self.attribute_name}_{self.path}_{idx}"
idx += 1
objectspace.jinja[jinja_path] = self.jinja
default = {
"function": function,
"params": {
"__internal_jinja": jinja_path,
"__internal_type": return_type,
"__internal_multi": multi,
"__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
if self.ori_path is None:
path = self.path
else:
path = self.ori_path
for sub_variable, identifier, true_path in get_jinja_variable_to_param(
path,
self.jinja,
objectspace,
variable.xmlfiles,
objectspace.functions,
self.version,
self.namespace,
):
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 self.version != "1.0":
default["params"][true_path]["propertyerror"] = False
default["params"][true_path]["optional"] = True
if identifier:
default["params"][true_path]["identifier"] = identifier
return default
def to_function(
self,
objectspace,
) -> dict:
if self.attribute_name in ["default", "secret_manager"]:
if self.return_type:
raise Exception("return_type not allowed!")
variable = objectspace.paths[self.path]
return_type = variable.type
if self.inside_list:
multi = False
elif self.path in objectspace.followers:
multi = objectspace.multis[self.path] == "submulti"
else:
multi = self.path in objectspace.multis
return self._jinja_to_function(
"jinja_to_function",
return_type,
multi,
objectspace,
)
elif self.attribute_name == "validators":
if self.return_type:
raise Exception("pfff")
return self._jinja_to_function(
"valid_with_jinja",
"string",
False,
objectspace,
)
elif self.attribute_name in PROPERTY_ATTRIBUTE:
if self.return_type:
raise Exception("return_type not allowed!")
return self._jinja_to_function(
"jinja_to_property",
"string",
False,
objectspace,
add_help=True,
params={None: [self.attribute_name], "when": True, "inverse": False},
)
elif self.attribute_name == "choices":
return_type = self.return_type
if return_type is None:
return_type = "string"
return self._jinja_to_function(
"jinja_to_function",
return_type,
not self.inside_list,
objectspace,
)
elif self.attribute_name == "dynamic":
return self._jinja_to_function(
"jinja_to_function",
"string",
True,
objectspace,
)
raise Exception("hu?")
class _VariableCalculation(Calculation):
variable: StrictStr
propertyerror: bool = True
allow_none: bool = False
def get_variable(
self,
objectspace,
) -> "Variable":
if self.ori_path is None:
path = self.path
else:
path = self.ori_path
if self.version != "1.0" and objectspace.paths.regexp_relative.search(
self.variable
):
variable_full_path = objectspace.paths.get_full_path(
self.variable,
path,
)
elif self.version == "1.0" and "{{ suffix }}" in self.variable:
variable_full_path = self.variable.replace(
"{{ suffix }}", "{{ identifier }}"
)
else:
variable_full_path = self.variable
variable, identifier = objectspace.paths.get_with_dynamic(
variable_full_path,
path,
self.version,
self.namespace,
self.xmlfiles,
)
if variable and not isinstance(variable, objectspace.variable):
if isinstance(variable, objectspace.family):
msg = _(
'a variable "{0}" is needs in attribute "{1}" for "{2}" but it\'s a family'
).format(variable_full_path, self.attribute_name, self.path)
raise DictConsistencyError(msg, 47, self.xmlfiles)
else:
msg = _('unknown object "{0}" in attribute "{1}" for "{2}"').format(
variable, self.attribute_name, self.path
)
raise DictConsistencyError(msg, 48, self.xmlfiles)
return variable_full_path, variable, identifier
def get_params(
self,
objectspace,
variable_in_calculation_path: str,
variable_in_calculation: "Variable",
variable_in_calculation_identifier: Optional[str],
):
if not variable_in_calculation:
if not objectspace.force_optional:
if self.ori_path is None:
path = self.path
else:
path = self.ori_path
msg = _(
'variable "{0}" has an attribute "{1}" calculated with the unknown variable "{2}"'
).format(path, self.attribute_name, self.variable)
raise DictConsistencyError(msg, 88, self.xmlfiles)
return {None: [["example"]]}
param = {
"type": "variable",
"variable": variable_in_calculation,
"propertyerror": self.propertyerror,
}
if isinstance(self, VariableCalculation) and self.optional:
param["optional"] = self.optional
if variable_in_calculation_identifier:
param["identifier"] = variable_in_calculation_identifier
params = {None: [param]}
if self.default_values:
params["__default_value"] = self.default_values
if self.allow_none:
params["allow_none"] = True
self.check_multi(
objectspace, variable_in_calculation_path, variable_in_calculation
)
if self.path in objectspace.followers:
multi = objectspace.multis[self.path] == "submulti"
else:
multi = self.path in objectspace.multis
if multi and not self.inside_list:
params["__internal_multi"] = True
return params
def check_multi(
self, objectspace, variable_in_calculation_path, variable_in_calculation
):
if self.ori_path is None:
path = self.path
else:
path = self.ori_path
local_variable = objectspace.paths[path]
local_variable_multi, variable_in_calculation_multi = (
calc_multi_for_type_variable(
local_variable,
variable_in_calculation_path,
variable_in_calculation,
objectspace,
)
)
if self.attribute_name == "default":
if variable_in_calculation_multi == "submulti":
if objectspace.paths.is_dynamic(variable_in_calculation.path):
msg = _(
'the variable "{0}" has an invalid "{1}" the variable "{2}" is in a sub dynamic option'
).format(
local_variable.path,
self.attribute_name,
variable_in_calculation.path,
)
raise DictConsistencyError(msg, 69, self.xmlfiles)
else:
msg = _(
'the leader "{0}" has an invalid "{1}" the follower "{2}" is a multi'
).format(
local_variable.path,
self.attribute_name,
variable_in_calculation.path,
)
raise DictConsistencyError(msg, 74, self.xmlfiles)
if not self.inside_list:
if local_variable_multi != variable_in_calculation_multi:
if local_variable_multi:
self.check_variable_in_calculation_multi(
local_variable.path,
variable_in_calculation_path,
variable_in_calculation_multi,
)
self.check_variable_in_calculation_not_multi(
local_variable.path,
variable_in_calculation_path,
variable_in_calculation_multi,
)
else:
self.check_variable_in_calculation_in_list_not_multi(
local_variable.path,
variable_in_calculation_path,
variable_in_calculation_multi,
)
elif self.attribute_name in ["choices", "dynamic"]:
# calculated variable must be a multi
if not self.inside_list:
self.check_variable_in_calculation_multi(
local_variable.path,
variable_in_calculation_path,
variable_in_calculation_multi,
)
else:
self.check_variable_in_calculation_in_list_not_multi(
local_variable.path,
variable_in_calculation_path,
variable_in_calculation_multi,
)
elif variable_in_calculation_multi is True:
msg = _(
'the variable "{0}" has an invalid attribute "{1}", the variable "{2}" must not be multi'
).format(
local_variable.path, self.attribute_name, variable_in_calculation_path
)
raise DictConsistencyError(msg, 23, self.xmlfiles)
def check_variable_in_calculation_multi(
self,
local_variable_path,
variable_in_calculation_path,
variable_in_calculation_multi,
):
if variable_in_calculation_multi is False:
msg = _(
'the variable "{0}" has an invalid attribute "{1}", the variable must not be a multi or the variable "{2}" must be multi'
).format(
local_variable_path, self.attribute_name, variable_in_calculation_path
)
raise DictConsistencyError(msg, 20, self.xmlfiles)
def check_variable_in_calculation_not_multi(
self,
local_variable_path,
variable_in_calculation_path,
variable_in_calculation_multi,
):
if variable_in_calculation_multi is True:
msg = _(
'the variable "{0}" has an invalid attribute "{1}", the variable must be a multi or the variable "{2}" must not be multi'
).format(
local_variable_path, self.attribute_name, variable_in_calculation_path
)
raise DictConsistencyError(msg, 21, self.xmlfiles)
def check_variable_in_calculation_in_list_not_multi(
self,
local_variable_path,
variable_in_calculation_path,
variable_in_calculation_multi,
):
if variable_in_calculation_multi is True:
msg = _(
'the variable "{0}" has an invalid attribute "{1}", the variable "{2}" is multi but is inside a list'
).format(
local_variable_path, self.attribute_name, variable_in_calculation_path
)
raise DictConsistencyError(msg, 18, self.xmlfiles)
class VariableCalculation(_VariableCalculation):
attribute_name: Literal["default", "choices", "dynamic"]
optional: bool = False
description: Optional[StrictStr] = None
def to_function(
self,
objectspace,
) -> dict:
if self.attribute_name != "default" and self.optional:
msg = _(
'"{0}" attribut shall not have an "optional" attribute for variable "{1}"'
).format(self.attribute_name, self.variable)
raise DictConsistencyError(msg, 33, self.xmlfiles)
(
variable_in_calculation_path,
variable_in_calculation,
variable_in_calculation_identifier,
) = self.get_variable(objectspace)
if (
not variable_in_calculation
and self.optional
or (objectspace.force_optional and self.attribute_name == "default")
):
raise VariableCalculationDependencyError()
if variable_in_calculation and self.attribute_name == "default":
local_variable = objectspace.paths[self.path]
if CONVERT_OPTION.get(local_variable.type, {}).get(
"func", str
) != CONVERT_OPTION.get(variable_in_calculation.type, {}).get("func", str):
msg = _(
'variable "{0}" has a default value calculated with "{1}" which has incompatible type'
).format(self.path, self.variable)
raise DictConsistencyError(msg, 67, self.xmlfiles)
params = self.get_params(
objectspace,
variable_in_calculation_path,
variable_in_calculation,
variable_in_calculation_identifier,
)
return {
"function": "calc_value",
"params": params,
}
class VariablePropertyCalculation(_VariableCalculation):
# For python 3.9 attribute_name: Literal[*PROPERTY_ATTRIBUTE]
attribute_name: Literal["frozen", "hidden", "disabled", "mandatory"]
when: Any = undefined
when_not: Any = undefined
description: Optional[StrictStr] = None
def to_function(
self,
objectspace,
) -> dict:
(
variable_in_calculation_path,
variable_in_calculation,
variable_in_calculation_identifier,
) = self.get_variable(objectspace)
params = self.get_params(
objectspace,
variable_in_calculation_path,
variable_in_calculation,
variable_in_calculation_identifier,
)
if objectspace.force_optional and (
not params[None] or "variable" not in params[None][0]
):
params = {None: [None, None, False]}
else:
variable = params[None][0]["variable"]
if self.when is not undefined:
if self.version == "1.0":
msg = _(
'"when" is not allowed in format version 1.0 for attribute "{0}" for variable "{1}"'
).format(self.attribute_name, self.path)
raise DictConsistencyError(msg, 103, variable.xmlfiles)
if self.when_not is not undefined:
msg = _(
'the variable "{0}" has an invalid attribute "{1}", "when" and "when_not" cannot set together'
).format(self.path, self.attribute_name)
raise DictConsistencyError(msg, 31, variable.xmlfiles)
when = self.when
inverse = False
elif self.when_not is not undefined:
if self.version == "1.0":
msg = _(
'"when_not" is not allowed in format version 1.0 for attribute "{0}" for variable "{1}"'
).format(self.attribute_name, self.path)
raise DictConsistencyError(msg, 104, variable.xmlfiles)
when = self.when_not
inverse = True
else:
if variable.multi:
when = []
else:
if variable.type != "boolean":
raise Exception("only boolean!")
when = True
inverse = False
params["when"] = when
params["inverse"] = inverse
params[None].insert(0, self.attribute_name)
func = "variable_to_property"
return {
"function": func,
"params": params,
"help": func,
}
class InformationCalculation(Calculation):
attribute_name: Literal["default", "choice", "dynamic"]
information: StrictStr
variable: Optional[StrictStr]
description: Optional[StrictStr] = None
def to_function(
self,
objectspace,
) -> dict:
params = {
None: [
{
"type": "information",
"information": self.information,
}
]
}
if self.variable:
if self.ori_path is None:
path = self.path
else:
path = self.ori_path
variable, identifier = objectspace.paths.get_with_dynamic(
self.variable,
path,
self.version,
self.namespace,
self.xmlfiles,
)
if variable is None:
if not objectspace.force_optional:
msg = _(
'cannot find variable "{0}" for the information "{1}" when calculating "{2}"'
).format(self.variable, self.information, self.attribute_name)
raise DictConsistencyError(msg, 40, variable.xmlfiles)
if identifier is not None:
msg = _(
'identifier not allowed for the information "{0}" when calculating "{1}"'
).format(self.information, self.attribute_name)
raise DictConsistencyError(msg, 41, variable.xmlfiles)
if variable:
params[None][0]["variable"] = variable
if self.default_values:
params["__default_value"] = self.default_values
return {
"function": "calc_value",
"params": params,
}
class _IdentifierCalculation(Calculation):
identifier: Optional[int] = None
def get_identifier(self) -> dict:
identifier = {"type": "identifier"}
if self.identifier is not None:
identifier["identifier"] = self.identifier
return identifier
class IdentifierCalculation(_IdentifierCalculation):
attribute_name: Literal["default", "choice", "dynamic"]
description: Optional[StrictStr] = None
def to_function(
self,
objectspace,
) -> dict:
identifier = {"type": "identifier"}
if self.identifier is not None:
identifier["identifier"] = self.identifier
return {
"function": "calc_value",
"params": {None: [self.get_identifier()]},
}
class IdentifierPropertyCalculation(_IdentifierCalculation):
# for python 3.9 attribute_name: Literal[*PROPERTY_ATTRIBUTE]
attribute_name: Literal["frozen", "hidden", "disabled", "mandatory"]
when: Any = undefined
when_not: Any = undefined
description: Optional[StrictStr] = None
def to_function(
self,
objectspace,
) -> dict:
if self.version == "1.0":
msg = _(
'"when" is not allowed in format version 1.0 for attribute "{0}"'
).format(self.attribute_name)
raise DictConsistencyError(msg, 105, variable.xmlfiles)
if self.when is not undefined:
if self.when_not is not undefined:
msg = _(
'the identifier has an invalid attribute "{0}", "when" and "when_not" cannot set together'
).format(self.attribute_name)
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 = _(
'the identifier has an invalid attribute "{0}", "when" and "when_not" cannot set together'
).format(self.attribute_name)
raise DictConsistencyError
params = {
None: [self.attribute_name, self.get_identifier()],
"when": when,
"inverse": inverse,
}
return {
"function": "variable_to_property",
"params": params,
"help": "variable_to_property",
}
class IndexCalculation(Calculation):
attribute_name: Literal["default", "choice", "dynamic"]
description: Optional[StrictStr] = None
def to_function(
self,
objectspace,
) -> dict:
if self.path not in objectspace.followers:
msg = _(
'the variable "{0}" is not a follower, so cannot have index type for "{1}"'
).format(self.path, self.attribute_name)
raise DictConsistencyError(msg, 60, self.xmlfiles)
return {
"function": "calc_value",
"params": {None: [{"type": "index"}]},
}
class NamespaceCalculation(Calculation):
attribute_name: Literal["default", "secret_manager"]
description: Optional[StrictStr] = None
def to_function(
self,
objectspace,
) -> dict:
namespace = self.namespace
if namespace:
namespace = objectspace.paths[namespace].description
return {
"function": "calc_value",
"params": {None: [namespace]},
}
CALCULATION_TYPES = {
"jinja": JinjaCalculation,
"information": InformationCalculation,
"variable": VariableCalculation,
"identifier": IdentifierCalculation,
# FOR VERSION 1.0
"suffix": IdentifierCalculation,
"index": IndexCalculation,
"namespace": NamespaceCalculation,
}
CALCULATION_PROPERTY_TYPES = {
"jinja": JinjaCalculation,
"information": InformationCalculation,
"variable": VariablePropertyCalculation,
"identifier": IdentifierPropertyCalculation,
"index": IndexCalculation,
}
BASETYPE_CALC = Union[StrictBool, StrictInt, StrictFloat, StrictStr, Calculation, None]
SECRET_BASETYPE_CALC = Union[StrictStr, JinjaCalculation]
class Family(BaseModel):
name: str
# informations
description: Optional[str] = None
help: Optional[str] = None
mode: Optional[str] = None
# validation
type: Literal["family", "leadership", "dynamic"] = "family"
# properties
hidden: Union[bool, Calculation] = False
disabled: Union[bool, Calculation] = False
# others
namespace: Optional[str]
path: str
version: str
xmlfiles: List[str] = []
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
class Dynamic(Family):
# None only for format 1.0
variable: str = None
dynamic: Union[List[Union[StrictStr, Calculation]], Calculation]
class Variable(BaseModel):
name: str
# user informations
description: Optional[str] = None
help: Optional[str] = None
mode: Optional[str] = None
examples: Optional[list] = None
test: Optional[list] = None
# validations
## type will be set dynamically in `annotator/value.py`, default is None
type: str = None
params: Optional[List[Param]] = None
regexp: Optional[str] = None
choices: Optional[Union[List[BASETYPE_CALC], Calculation]] = None
multi: Optional[bool] = None
validators: Optional[List[Calculation]] = None
# value
default: Union[List[BASETYPE_CALC], BASETYPE_CALC] = None
secret_manager: Optional[JinjaCalculation] = None
# properties
auto_save: bool = False
mandatory: Union[None, bool, Calculation] = None
empty: Union[None, bool, Calculation] = True
unique: Optional[bool] = None
hidden: Union[bool, Calculation] = False
disabled: Union[bool, Calculation] = False
# others
path: str
namespace: Optional[str]
version: str
xmlfiles: List[str] = []
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
class SymLink(BaseModel):
type: Literal["symlink"] = "symlink"
name: str
path: str
opt: Variable
namespace: Optional[str]
version: str
xmlfiles: List[str] = []
model_config = ConfigDict(extra="forbid")

238
src/rougail/convert/path.py Normal file
View file

@ -0,0 +1,238 @@
"""
Copyright (C) 2024-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from typing import (
Any,
Dict,
List,
Union,
)
import logging
from re import compile, findall
from ..i18n import _
from .object_model import Family, Variable
from ..tiramisu import normalize_family
class Paths:
regexp_relative = compile(r"^_*\.(.*)$")
def __init__(
self,
default_namespace: str,
) -> None:
self._data: Dict[str, Union[Variable, Family]] = {}
self._dynamics: Dict[str:str] = {}
if default_namespace is not None:
default_namespace = normalize_family(default_namespace)
self.default_namespace = default_namespace
def has_value(self) -> bool:
return self._data != {}
def add(
self,
path: str,
data: Any,
is_dynamic: bool,
dynamic: str,
*,
force: bool = False,
) -> None:
self._data[path] = data
if not force and is_dynamic:
self._dynamics[path] = dynamic
def get_full_path(
self,
path: str,
current_path: str,
):
if "{{ suffix }}" in path:
# version 1.0 and 1.1
path = path.replace("{{ suffix }}", "{{ identifier }}")
relative, subpath = path.split(".", 1)
relative_len = len(relative)
path_len = current_path.count(".")
if path_len + 1 == relative_len:
return subpath
parent_path = current_path.rsplit(".", relative_len)[0]
return parent_path + "." + subpath
def get_with_dynamic(
self,
path: str,
current_path: str,
version: str,
namespace: str,
xmlfiles: List[str],
) -> Any:
identifier = None
if version != "1.0" and self.regexp_relative.search(path):
path = self.get_full_path(
path,
current_path,
)
# elif identifier_path:
# path = f"{identifier_path}.{path}"
dynamic = None
# version 1.0
if version == "1.0":
if not path in self._data and "{{ suffix }}" not in path:
new_path = None
current_path = None
identifiers = []
for name in path.split("."):
parent_path = current_path
if current_path:
current_path += "." + name
else:
current_path = name
if current_path in self._data:
if new_path:
new_path += "." + name
else:
new_path = name
continue
for dynamic_path in self._dynamics:
if "." in dynamic_path:
parent_dynamic, name_dynamic = dynamic_path.rsplit(".", 1)
else:
parent_dynamic = None
name_dynamic = dynamic_path
if (
parent_dynamic == parent_path
and name_dynamic.endswith("{{ identifier }}")
and name == name_dynamic.replace("{{ identifier }}", "")
):
new_path += "." + name_dynamic
break
regexp = "^" + name_dynamic.replace("{{ identifier }}", "(.*)")
finded = findall(regexp, name)
if len(finded) != 1 or not finded[0]:
continue
if finded[0] == "{{ identifier }}":
identifiers.append(None)
else:
identifiers.append(finded[0])
if new_path is None:
new_path = name_dynamic
else:
new_path += "." + name_dynamic
parent_path = dynamic_path
break
else:
if new_path:
new_path += "." + name
else:
new_path = name
path = new_path
else:
identifiers = None
elif not path in self._data:
new_path = parent_path = current_path = None
identifiers = []
for name in path.split("."):
if current_path:
current_path += "." + name
else:
current_path = name
# parent_path, name_path = path.rsplit('.', 1)
if current_path in self._data:
if new_path:
new_path += "." + name
else:
new_path = name
parent_path = current_path
continue
for dynamic_path in self._dynamics:
if "." in dynamic_path:
parent_dynamic, name_dynamic = dynamic_path.rsplit(".", 1)
else:
parent_dynamic = None
name_dynamic = dynamic_path
if (
"{{ identifier }}" not in name_dynamic
or parent_path != parent_dynamic
):
continue
regexp = "^" + name_dynamic.replace("{{ identifier }}", "(.*)")
finded = findall(regexp, name)
if len(finded) != 1 or not finded[0]:
continue
if finded[0] == "{{ identifier }}":
identifiers.append(None)
else:
identifiers.append(finded[0])
if new_path is None:
new_path = name_dynamic
else:
new_path += "." + name_dynamic
parent_path = dynamic_path
break
else:
if new_path:
new_path += "." + name
else:
new_path = name
if "{{ identifier }}" in name:
identifiers.append(None)
parent_path = current_path
path = new_path
else:
identifiers = None
if path not in self._data:
return None, None
option = self._data[path]
option_namespace = option.namespace
if (
self.default_namespace not in [namespace, option_namespace]
and namespace != option_namespace
):
msg = _(
'A variable or a family located in the "{0}" namespace shall not be used in the "{1}" namespace'
).format(option_namespace, namespace)
raise DictConsistencyError(msg, 38, xmlfiles)
return option, identifiers
def __getitem__(
self,
path: str,
) -> Union[Family, Variable]:
if not path in self._data:
raise AttributeError(f"cannot find variable or family {path}")
return self._data[path]
def __contains__(
self,
path: str,
) -> bool:
return path in self._data
def __delitem__(
self,
path: str,
) -> None:
logging.info("remove empty family %s", path)
del self._data[path]
def is_dynamic(self, path: str) -> bool:
return path in self._dynamics
def get(self):
return self._data.values()

View file

@ -10,7 +10,7 @@ Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -30,10 +30,10 @@ from typing import Optional, Union
from json import dumps
from os.path import isfile, basename
from .i18n import _
from .error import DictConsistencyError, VariableCalculationDependencyError
from .utils import normalize_family
from .object_model import Calculation, CONVERT_OPTION
from ..i18n import _
from ..error import DictConsistencyError, VariableCalculationDependencyError
from ..tiramisu import normalize_family, CONVERT_OPTION
from .object_model import Calculation
class BaseElt: # pylint: disable=R0903
@ -73,12 +73,7 @@ class TiramisuReflector:
"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"
"from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription",
]
)
if funcs_paths:
@ -87,7 +82,7 @@ class TiramisuReflector:
continue
self.text["header"].append(f"load_functions('{funcs_path}')")
if self.objectspace.export_with_import:
if objectspace.main_namespace:
if self.objectspace.has_namespace:
self.text["header"].extend(
[
"try:",
@ -295,40 +290,14 @@ class Common:
):
"""Populate variable parameters"""
if not isinstance(param, dict):
if isinstance(param, str):
value = self.convert_str(param)
else:
value = param
return f"ParamValue({value})"
param = {
"type": "any",
"value": param,
}
if param["type"] == "value":
return f"ParamValue({param['value']})"
if param["type"] == "information":
# default? really?
if self.elt.multi:
default = []
else:
default = None
if "variable" in param:
if param["variable"].path == self.elt.path:
return f'ParamSelfInformation("{param["information"]}", {default})'
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})'
return self.build_information_param(param)
if param["type"] == "identifier":
if "identifier" in param and param["identifier"] != None:
return f"ParamIdentifier(identifier_index={param['identifier']})"
@ -336,13 +305,7 @@ class Common:
if param["type"] == "index":
return "ParamIndex()"
if param["type"] == "variable":
return self.build_option_param(
param["variable"],
param.get("propertyerror", True),
param.get("identifier"),
param.get("dynamic"),
param.get("whole", False),
)
return self.build_variable_param(param)
if param["type"] == "any":
if isinstance(param["value"], str):
value = self.convert_str(param["value"])
@ -351,15 +314,40 @@ class Common:
return "ParamValue(" + value + ")"
raise Exception("pfff")
def build_option_param(
def build_information_param(self, param: dict) -> str:
# default? really?
if self.elt.multi:
default = []
else:
default = None
if "variable" in param:
information_variable_path = param["variable"].path
if information_variable_path == self.elt.path:
return f'ParamSelfInformation("{param["information"]}", {default})'
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:
# if we want to get information from the a parent family
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})'
def build_variable_param(
self,
variable,
propertyerror,
identifier: Optional[str],
dynamic,
whole: bool,
param: dict,
) -> str:
"""build variable parameters"""
variable = param["variable"]
whole = param.get("whole", False)
if variable.path == self.elt.path:
return f"ParamSelfOption(whole={whole})"
if whole:
@ -369,7 +357,13 @@ class Common:
self.calls, self.elt.path
)
params = [f"{option_name}"]
identifier = param.get("identifier")
if identifier is not None:
if not self.objectspace.paths.is_dynamic(variable.path):
msg = _("internal error, {0} is not a dynamic variable").format(
variable.path
)
raise DictConsistencyError(msg, 49, self.elt.xmlfiles)
param_type = "ParamDynOption"
identifiers = []
for ident in identifier:
@ -377,9 +371,11 @@ class Common:
ident = self.convert_str(ident)
identifiers.append(str(ident))
params.append("[" + ", ".join(identifiers) + "]")
if param.get("optional", False):
params.append("optional=True")
else:
param_type = "ParamOption"
if not propertyerror:
if not param.get("propertyerror", True):
params.append("notraisepropertyerror=True")
return f'{param_type}({", ".join(params)})'
@ -393,7 +389,7 @@ class Common:
kwargs = []
if "params" in child:
for key, value in child["params"].items():
if not key:
if key is None:
for val in value:
new_args.append(self.populate_param(val))
else:

View file

@ -9,7 +9,7 @@ Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -26,13 +26,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from .i18n import _
def display_xmlfiles(xmlfiles: list) -> str:
"""The function format xmlfiles informations to generate errors"""
if len(xmlfiles) == 1:
return '"' + xmlfiles[0] + '"'
return '"' + '", "'.join(xmlfiles[:-1]) + '"' + " and " + '"' + xmlfiles[-1] + '"'
from .tiramisu import display_xmlfiles
class ConfigError(Exception):
@ -69,10 +63,6 @@ class DictConsistencyError(Exception):
self.errno = errno
class UpgradeError(Exception):
"""Error during XML upgrade"""
## ---- generic exceptions ----
@ -81,6 +71,10 @@ class NotFoundError(Exception):
pass
class ExtentionError(Exception):
pass
## ---- specific exceptions ----

View file

@ -8,7 +8,7 @@ Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the

View file

@ -1,765 +0,0 @@
"""Rougail object model
Silique (https://www.silique.fr)
Copyright (C) 2023-2024
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from typing import Optional, Union, get_type_hints, Any, Literal, List, Dict, Iterator
from pydantic import (
BaseModel,
StrictBool,
StrictInt,
StrictFloat,
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:
"""Boolean coercion. The Rougail XML may contain srings like `True` or `False`"""
if isinstance(value, bool):
return value
value = value.lower()
if value == "true":
return True
elif value == "false":
return False
elif value in ["", None]:
return None
raise Exception(f'unknown boolean value "{value}"')
CONVERT_OPTION = {
"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", 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},
example="example.net",
),
"hostname": dict(
opttype="DomainnameOption",
initkwargs={"type": "hostname", "allow_ip": False},
example="example",
),
"web_address": dict(
opttype="URLOption",
initkwargs={"allow_ip": False, "allow_without_dot": True},
example="https://example.net",
),
"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,
example="644",
),
"choice": dict(opttype="ChoiceOption", example="a_choice"),
"regexp": dict(opttype="RegexpOption"),
#
"symlink": dict(opttype="SymLinkOption"),
}
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: Union[BASETYPE, List[BASETYPE]]
class VariableParam(Param):
type: str
variable: str
propertyerror: bool = True
whole: bool = False
optional: bool = False
class IdentifierParam(Param):
type: str
identifier: Optional[int] = None
def __init__(
self,
**kwargs,
) -> None:
if not kwargs["family_is_dynamic"]:
msg = f'identifier 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):
type: str
information: str
variable: Optional[str] = None
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,
"variable": VariableParam,
"identifier": IdentifierParam,
"information": InformationParam,
"index": IndexParam,
}
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")
def get_realpath(
self,
path: str,
) -> str:
return get_realpath(path, self.path_prefix)
def get_params(self, objectspace):
if not self.params:
return {}
params = {}
for param_obj in self.params:
param = param_obj.model_dump()
if param.get("type") == "variable":
if self.ori_path is None:
path = self.path
else:
path = self.ori_path
variable, identifier = 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"):
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 identifier:
param["identifier"] = identifier
if param.get("type") == "information":
if param["variable"]:
if self.ori_path is None:
path = self.path
else:
path = self.ori_path
variable, identifier = 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 identifier:
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
return params
class JinjaCalculation(Calculation):
attribute_name: Literal[
"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,
function,
return_type,
multi,
objectspace,
*,
add_help=False,
params: Optional[dict] = None,
):
variable = objectspace.paths[self.path]
jinja_path = f"{self.attribute_name}_{self.path}"
idx = 0
while jinja_path in objectspace.jinja:
jinja_path = f"{self.attribute_name}_{self.path}_{idx}"
idx += 1
objectspace.jinja[jinja_path] = self.jinja
default = {
"function": function,
"params": {
"__internal_jinja": jinja_path,
"__internal_type": return_type,
"__internal_multi": multi,
"__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
if self.ori_path is None:
path = self.path
else:
path = self.ori_path
for sub_variable, identifier, true_path in get_jinja_variable_to_param(
path,
self.jinja,
objectspace,
variable.xmlfiles,
objectspace.functions,
self.path_prefix,
self.version,
self.namespace,
):
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 self.version != "1.0":
default["params"][true_path]["propertyerror"] = False
if identifier:
default["params"][true_path]["identifier"] = identifier
return default
def to_function(
self,
objectspace,
) -> dict:
if self.attribute_name == "default":
if self.return_type:
raise Exception("return_type not allowed!")
variable = objectspace.paths[self.path]
return_type = variable.type
if self.inside_list:
multi = False
elif self.path in objectspace.followers:
multi = objectspace.multis[self.path] == "submulti"
else:
multi = self.path in objectspace.multis
return self._jinja_to_function(
"jinja_to_function",
return_type,
multi,
objectspace,
)
elif self.attribute_name == "validators":
if self.return_type:
raise Exception("pfff")
return self._jinja_to_function(
"valid_with_jinja",
"string",
False,
objectspace,
)
elif self.attribute_name in PROPERTY_ATTRIBUTE:
if self.return_type:
raise Exception("return_type not allowed!")
return self._jinja_to_function(
"jinja_to_property",
"string",
False,
objectspace,
add_help=True,
params={None: [self.attribute_name], "when": True, "inverse": False},
)
elif self.attribute_name == "choices":
return_type = self.return_type
if return_type is None:
return_type = "string"
return self._jinja_to_function(
"jinja_to_function",
return_type,
not self.inside_list,
objectspace,
)
elif self.attribute_name == "dynamic":
return self._jinja_to_function(
"jinja_to_function",
"string",
True,
objectspace,
)
raise Exception("hu?")
class _VariableCalculation(Calculation):
variable: StrictStr
propertyerror: bool = True
allow_none: bool = False
def get_variable(
self,
objectspace,
) -> "Variable":
if self.ori_path is None:
path = self.path
else:
path = self.ori_path
variable, identifier = 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, identifier
def get_params(
self,
objectspace,
variable: "Variable",
identifier: Optional[str],
*,
needs_multi: Optional[bool] = None,
):
if not variable:
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,
"propertyerror": self.propertyerror,
}
if identifier:
param["identifier"] = identifier
params = {None: [param]}
if self.default_values:
params["__default_value"] = self.default_values
if self.allow_none:
params["allow_none"] = True
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 (
identifier is None or identifier[-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 identifier and "{{ identifier }}" in identifier:
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, identifier = self.get_variable(objectspace)
if not variable and self.optional:
raise VariableCalculationDependencyError()
params = self.get_params(
objectspace,
variable,
identifier,
)
return {
"function": "calc_value",
"params": params,
}
class VariablePropertyCalculation(_VariableCalculation):
attribute_name: Literal[*PROPERTY_ATTRIBUTE]
when: Any = undefined
when_not: Any = undefined
def to_function(
self,
objectspace,
) -> dict:
variable, identifier = self.get_variable(objectspace)
params = self.get_params(
objectspace,
variable,
identifier,
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", "choice", "dynamic"]
information: StrictStr
variable: Optional[StrictStr]
def to_function(
self,
objectspace,
) -> dict:
params = {
None: [
{
"type": "information",
"information": self.information,
}
]
}
if self.variable:
if self.ori_path is None:
path = self.path
else:
path = self.ori_path
variable, identifier = objectspace.paths.get_with_dynamic(
self.variable,
self.path_prefix,
path,
self.version,
self.namespace,
self.xmlfiles,
)
if variable is None or identifier is not None:
raise Exception("pfff")
params[None][0]["variable"] = variable
if self.default_values:
params["__default_value"] = self.default_values
return {
"function": "calc_value",
"params": params,
}
class _IdentifierCalculation(Calculation):
identifier: Optional[int] = None
def get_identifier(self) -> dict:
identifier = {"type": "identifier"}
if self.identifier is not None:
identifier["identifier"] = self.identifier
return identifier
class IdentifierCalculation(_IdentifierCalculation):
attribute_name: Literal["default", "choice", "dynamic"]
def to_function(
self,
objectspace,
) -> dict:
identifier = {"type": "identifier"}
if self.identifier is not None:
identifier["identifier"] = self.identifier
return {
"function": "calc_value",
"params": {None: [self.get_identifier()]},
}
class IdentifierPropertyCalculation(_IdentifierCalculation):
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 identifier 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 identifier has an invalid attribute "{self.attribute_name}", when and when_not cannot set together'
raise DictConsistencyError
params = {
None: [self.attribute_name, self.get_identifier()],
"when": when,
"inverse": inverse,
}
return {
"function": "variable_to_property",
"params": params,
"help": "variable_to_property",
}
class IndexCalculation(Calculation):
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"}]},
}
CALCULATION_TYPES = {
"jinja": JinjaCalculation,
"variable": VariableCalculation,
"information": InformationCalculation,
"identifier": IdentifierCalculation,
"suffix": IdentifierCalculation,
"index": IndexCalculation,
}
CALCULATION_PROPERTY_TYPES = {
"jinja": JinjaCalculation,
"variable": VariablePropertyCalculation,
"information": InformationCalculation,
"identifier": IdentifierPropertyCalculation,
"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] = []
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True)
class Dynamic(Family):
# None only for format 1.0
variable: str = None
dynamic: Union[List[Union[StrictStr, Calculation]], Calculation]
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: 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] = None
empty: Union[None, bool, Calculation] = True
auto_save: bool = False
mode: Optional[str] = None
test: Optional[list] = None
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 SymLink(BaseModel):
type: Literal["symlink"] = "symlink"
name: str
path: str
opt: Variable
namespace: Optional[str]
version: str
path_prefix: Optional[str]
xmlfiles: List[str] = []
model_config = ConfigDict(extra="forbid")

View file

@ -1,6 +1,6 @@
"""
Silique (https://www.silique.fr)
Copyright (C) 2024
Copyright (C) 2024-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -16,4 +16,7 @@ You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from .update import RougailUpgrade
from .object_model import Variable, Family
__all__ = ("Variable", "Family")

View file

@ -1,7 +1,7 @@
"""Annotate to add specify attribute for tiramisu-cmdline
Silique (https://www.silique.fr)
Copyright (C) 2024
Copyright (C) 2024-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -50,7 +50,6 @@ class Annotator(Walk):
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)
@ -79,34 +78,11 @@ class Annotator(Walk):
path = alternative_name
else:
path = variable_path.rsplit(".", 1)[0] + "." + alternative_name
self.objectspace.version = variable.version
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(
_(
'negative_description is mandatory for boolean variable, but "{0}" hasn\'t'
).format(variable.path),
200,
variable.xmlfiles,
)
return
if variable.type != "boolean":
raise DictConsistencyError(
_(
'negative_description is only available for boolean variable, but "{0}" is "{1}"'
).format(variable.path, variable.type),
201,
variable.xmlfiles,
)
self.objectspace.informations.add(
variable.path, "negative_description", variable.negative_description
)

View file

@ -2,7 +2,7 @@
Config file for Rougail-structural_commandline
Silique (https://www.silique.fr)
Copyright (C) 2024
Copyright (C) 2024-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -27,12 +27,13 @@ def get_rougail_config(
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",
"name": "cmdline",
"process": "structural",
"options": options,
"level": 20,

View file

@ -1,7 +1,7 @@
"""Annotate to add specify attribute for tiramisu-cmdline
Silique (https://www.silique.fr)
Copyright (C) 2024
Copyright (C) 2024-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -24,11 +24,7 @@ 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")

View file

@ -0,0 +1,155 @@
"""
Silique (https://www.silique.fr)
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from typing import Union, List, Iterator, Optional
from itertools import chain
from pathlib import Path
from ruamel.yaml import YAML
from ..tiramisu import normalize_family
from ..convert.path import Paths
from ..error import DictConsistencyError
from ..i18n import _
class Walker:
def __init__(
self,
convert,
) -> None:
"""Parse directories content"""
self.convert = convert
self.yaml = YAML()
rougailconfig = self.convert.rougailconfig
self.sort_structural_files_all = rougailconfig["sort_structural_files_all"]
if rougailconfig["main_namespace"]:
self.convert.paths = Paths(rougailconfig["main_namespace"])
self.load_with_extra(
rougailconfig["extra_namespaces"],
rougailconfig["main_namespace"],
rougailconfig["main_structural_directories"],
)
else:
self.convert.namespace = None
namespace_path = ""
if namespace_path in self.convert.parents:
raise Exception("pfff")
for filename in self.get_sorted_filename(
rougailconfig["main_structural_directories"]
):
self.parse_variable_file(
filename,
namespace_path,
)
def load_with_extra(
self,
extra_structures: dict,
main_namespace: Optional[str] = None,
main_structures: Optional[List[str]] = None,
) -> None:
self.convert.has_namespace = True
if main_namespace:
directory_dict = chain(
(
(
main_namespace,
main_structures,
),
),
extra_structures.items(),
)
else:
directory_dict = extra_structures.items()
for namespace, extra_dirs in directory_dict:
# if namespace is None:
# self.convert.namespace = namespace
# else:
self.convert.namespace = normalize_family(namespace)
namespace_path = self.convert.namespace
if namespace_path in self.convert.parents:
raise Exception("pfff")
for idx, filename in enumerate(self.get_sorted_filename(extra_dirs)):
if not idx:
# create only for the first file
self.convert.create_namespace(namespace, namespace_path)
self.parse_variable_file(
filename,
namespace_path,
)
def get_sorted_filename(
self,
directories: Union[str, List[str]],
) -> Iterator[str]:
"""Sort filename"""
if not isinstance(directories, list):
directories = [directories]
if self.sort_structural_files_all:
filenames = {}
for directory_name in directories:
directory = Path(directory_name)
if not self.sort_structural_files_all:
filenames = {}
if directory.is_file():
self.get_filename(directory, filenames)
else:
for file_path in directory.iterdir():
self.get_filename(file_path, filenames)
if not self.sort_structural_files_all:
for filename in sorted(filenames):
yield filenames[filename]
if self.sort_structural_files_all:
for filename in sorted(filenames):
yield filenames[filename]
def get_filename(self, file_path, filenames: List[str]) -> None:
if file_path.suffix not in [".yml", ".yaml"]:
return
if file_path.name in filenames:
raise DictConsistencyError(
_("duplicate structural file name {0}").format(file_path.name),
78,
[filenames[file_path.name], str(file_path)],
)
filenames[file_path.name] = str(file_path)
def parse_variable_file(
self,
filename: str,
path: str,
) -> None:
"""Parse file"""
with open(filename, encoding="utf8") as file_fh:
objects = self.yaml.load(file_fh)
version = self.convert.validate_file_version(
objects,
filename,
)
if objects is None:
return
self.convert.parse_root_file(
filename,
path,
version,
objects,
)
__all__ = ("Walker",)

View file

@ -0,0 +1,102 @@
"""
Silique (https://www.silique.fr)
Copyright (C) 2024-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from ..utils import _
def get_rougail_config(
*,
backward_compatibility=True,
) -> dict:
if backward_compatibility:
main_namespace_default = "rougail"
else:
main_namespace_default = "null"
options = f"""
main_structural_directories:
description: {_("Directories where structural files are placed")}
type: unix_filename
alternative_name: m
params:
allow_relative: True
test_existence: True
multi: true
disabled:
jinja: >-
{{% if 'directory' not in step.structural %}}
directory is not in step.structural
{{% endif %}}
{{% if cli is defined and cli.versions is defined and cli.versions %}}
cli.versions is specified
{{% endif %}}
sort_structural_files_all:
description: {_("Sort structural from differents directories")}
default: false
disabled:
jinja: >-
{{% if 'directory' not in step.structural %}}
directory is not in step.structural
{{% endif %}}
main_namespace:
description: {_("Main namespace name")}
default: {main_namespace_default}
alternative_name: s
mandatory: false
disabled:
jinja: >-
{{% if 'directory' not in step.structural %}}
directory is not in step.structural
{{% endif %}}
extra_namespaces:
description: {_("Extra namespaces")}
type: leadership
disabled:
jinja: >-
{{% if 'directory' not in step.structural %}}
directory is not in step.structural
{{% endif %}}
names:
description: {_("Extra namespace name")}
alternative_name: xn
multi: true
mandatory: false
directories:
description: {_("Directories where extra structural files are placed")}
alternative_name: xd
type: unix_filename
params:
allow_relative: true
test_existence: true
types:
- directory
multi: true
"""
return {
"name": "directory",
"process": "structural",
"options": options,
"level": 5,
}
__all__ = "get_rougail_config"

View file

@ -9,7 +9,7 @@ Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -27,22 +27,135 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Any
try:
from tiramisu5 import DynOptionDescription, calc_value
except ModuleNotFoundError:
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 unicodedata import normalize, combining
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 import function_waiting_for_error
from tiramisu.error import ValueWarning, ConfigError, PropertiesOptionError
from .utils import normalize_family
from re import findall
from tiramisu import DynOptionDescription, calc_value, function_waiting_for_error
from tiramisu.error import (
ValueWarning,
ConfigError,
PropertiesOptionError,
CancelParam,
errors,
)
ori_raise_carry_out_calculation_error = errors.raise_carry_out_calculation_error
try:
from .i18n import _
except ModuleNotFoundError:
# FIXME
def _(msg):
return msg
def display_xmlfiles(xmlfiles: list) -> str:
"""The function format xmlfiles informations to generate errors"""
if len(xmlfiles) == 1:
return '"' + xmlfiles[0] + '"'
return '"' + '", "'.join(xmlfiles[:-1]) + '"' + " and " + '"' + xmlfiles[-1] + '"'
def convert_boolean(value: str) -> bool:
"""Boolean coercion. The Rougail XML may contain srings like `True` or `False`"""
if isinstance(value, bool):
return value
value = value.lower()
if value == "true":
return True
elif value == "false":
return False
elif value in ["", None]:
return None
raise Exception(_('unknown boolean value "{0}"').format(value))
CONVERT_OPTION = {
"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", 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},
example="example.net",
),
"hostname": dict(
opttype="DomainnameOption",
initkwargs={"type": "hostname", "allow_ip": False},
example="example",
),
"web_address": dict(
opttype="URLOption",
initkwargs={"allow_ip": False, "allow_without_dot": True},
example="https://example.net",
),
"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,
example="644",
),
"choice": dict(opttype="ChoiceOption", example="a_choice"),
"regexp": dict(opttype="RegexpOption"),
#
"symlink": dict(opttype="SymLinkOption"),
}
def get_identifier_from_dynamic_family(true_name, name) -> str:
if true_name == "{{ identifier }}":
return name
regexp = true_name.replace("{{ identifier }}", "(.*)")
finded = findall(regexp, name)
if len(finded) != 1 or not finded[0]:
return None
return finded[0]
def raise_carry_out_calculation_error(subconfig, *args, **kwargs):
try:
ori_raise_carry_out_calculation_error(subconfig, *args, **kwargs)
except ConfigError as err:
ymlfiles = subconfig.config_bag.context.get_values().get_information(
subconfig, "ymlfiles", []
)
raise ConfigError(_("{0} in {1}").format(err, display_xmlfiles(ymlfiles)))
errors.raise_carry_out_calculation_error = raise_carry_out_calculation_error
global func
@ -90,8 +203,10 @@ def test_propertyerror(value: Any) -> bool:
ENV.tests["propertyerror"] = test_propertyerror
def load_functions(path):
def load_functions(path, dict_func=None):
global _SourceFileLoader, _spec_from_loader, _module_from_spec, func
if dict_func is None:
dict_func = func
loader = _SourceFileLoader("func", path)
spec = _spec_from_loader(loader.name, loader)
func_ = _module_from_spec(spec)
@ -99,16 +214,78 @@ def load_functions(path):
for function in dir(func_):
if function.startswith("_"):
continue
func[function] = getattr(func_, function)
dict_func[function] = getattr(func_, function)
def rougail_calc_value(*args, __default_value=None, **kwargs):
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 tiramisu_display_name(
kls,
subconfig,
with_quote: bool = False,
) -> str:
"""Replace the Tiramisu display_name function to display path + description"""
config_bag = subconfig.config_bag
context = config_bag.context
values = context.get_values()
context_subconfig = context.get_root(config_bag)
doc = values.get_information(subconfig, "doc", None)
comment = doc if doc and doc != kls.impl_getname() else ""
if "{{ identifier }}" in comment and subconfig.identifiers:
comment = comment.replace("{{ identifier }}", str(subconfig.identifiers[-1]))
path_in_description = values.get_information(
context_subconfig, "path_in_description", True
)
if path_in_description or not comment:
comment = f" ({comment})" if comment else ""
if path_in_description is False:
path = kls.impl_getname()
else:
path = kls.impl_getpath()
if "{{ identifier }}" in path and subconfig.identifiers:
path = path.replace(
"{{ identifier }}", normalize_family(str(subconfig.identifiers[-1]))
)
else:
path = comment
comment = ""
if with_quote:
return f'"{path}"{comment}'
return f"{path}{comment}"
def rougail_calc_value(*args, __default_value=None, __internal_multi=False, **kwargs):
values = calc_value(*args, **kwargs)
if values is None and __internal_multi:
values = []
if __default_value is not None and values in [None, []]:
return __default_value
return values
def kw_to_string(kw, root=None):
for name, data in kw.items():
if root is None:
path = name
else:
path = root + "." + name
if isinstance(data, dict):
yield from kw_to_string(data, root=path)
else:
yield f"{path}={data}"
pass
@function_waiting_for_error
def jinja_to_function(
__internal_variable,
@ -128,9 +305,15 @@ def jinja_to_function(
if "." in key:
c_kw = kw
path, var = key.rsplit(".", 1)
if isinstance(value, CancelParam):
count_o_path = value.origin_path.count(".") - value.current_path.count(
"."
)
path = path.rsplit(".", count_o_path)[0]
for subkey in path.split("."):
c_kw = c_kw.setdefault(subkey, {})
c_kw[var] = value
if not isinstance(value, CancelParam):
c_kw[var] = value
else:
if key in kw:
raise ConfigError(
@ -140,12 +323,23 @@ def jinja_to_function(
try:
values = ENV.get_template(__internal_jinja).render(kw, **func).strip()
except Exception as err:
kw_str = ", ".join(kw_to_string(kw))
raise ConfigError(
f'cannot calculating "{__internal_attribute}" attribute for variable "{__internal_variable}" in {display_xmlfiles(__internal_files)}: {err}'
_(
'cannot calculating "{0}" attribute for variable "{1}" in {2} with parameters "{3}": {4}'
).format(
__internal_attribute,
__internal_variable,
display_xmlfiles(__internal_files),
kw_str,
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 != ""]
values = [
convert(val.strip()) for val in values.split("\n") if val.strip() != ""
]
if not values and __default_value is not None:
return __default_value
return values
@ -161,7 +355,7 @@ def jinja_to_function(
return values
def variable_to_property(prop, value, when, inverse):
def variable_to_property(prop, value, when, inverse, **kwargs):
if isinstance(value, PropertiesOptionError):
raise value from value
if inverse:
@ -200,6 +394,7 @@ 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
func["normalize_family"] = normalize_family
class ConvertDynOptionDescription(DynOptionDescription):
@ -239,3 +434,11 @@ class ConvertDynOptionDescription(DynOptionDescription):
self.convert_identifier_to_path(self.get_identifiers(subconfig)[-1]),
)
return display
def name_could_conflict(self, dynchild, child):
return (
get_identifier_from_dynamic_family(
dynchild.impl_getname(), child.impl_getname()
)
is not None
)

File diff suppressed because it is too large Load diff

424
src/rougail/user_datas.py Normal file
View file

@ -0,0 +1,424 @@
"""
Silique (https://www.silique.fr)
Copyright (C) 2022-2025
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 tiramisu import Calculation, owners
from tiramisu.error import (
PropertiesOptionError,
AttributeOptionError,
LeadershipError,
ConfigError,
CancelParam,
)
from .utils import undefined
from .tiramisu import (
normalize_family,
CONVERT_OPTION,
get_identifier_from_dynamic_family,
)
from .error import DictConsistencyError
from .i18n import _
class UserDatas:
def __init__(self, config) -> None:
self.config = config
def user_datas(
self,
user_datas: List[dict],
*,
return_values_not_error=False,
user_datas_type: str = "user_datas",
only_default: bool = False,
):
self.values = {}
self.errors = []
self.warnings = []
self.show_secrets = False
self.only_default = only_default
if user_datas_type == "user_datas":
self._populate_values(user_datas)
else:
self.values = user_datas
self._auto_configure_dynamics()
self._populate_config()
if return_values_not_error:
return self.values
else:
self._populate_error_warnings()
return {
"errors": self.errors,
"warnings": self.warnings,
}
def _populate_values(self, user_datas):
for datas in user_datas:
options = datas.get("options", {})
source = datas["source"]
for name, data in datas.get("values", {}).items():
self.values[name] = {
"source": source,
"values": data,
"options": options.copy(),
}
self.errors.extend(datas.get("errors", []))
self.warnings.extend(datas.get("warnings", []))
def _get_variable(self, config):
for subconfig in config:
if subconfig.isoptiondescription():
yield from self._get_variable(subconfig)
else:
yield subconfig
def _auto_configure_dynamics(self):
cache = {}
added = []
for path, data in list(self.values.items()):
try:
option = self.config.option(path)
option.name()
except (ConfigError, PropertiesOptionError):
pass
except AttributeError:
self._not_found_is_dynamic(self.config, path, cache, added, data)
def _not_found_is_dynamic(self, config, path, cache, added, data):
"""if path is not found, check if parent is a dynamic family"""
current_path = ""
identifiers = []
# get parent
for name in path.split(".")[:-1]:
if current_path:
current_path += "."
current_path += name
if current_path in cache:
config, identifier = cache[current_path]
identifiers.append(identifier)
continue
tconfig = config.option(name)
try:
tconfig.group_type()
# object exists, so current config is the temporary config
config = tconfig
if config.isdynamic(only_self=True):
identifiers.append(config.identifiers()[-1])
except AttributeError:
# try to found the good dynamic family
for tconfig in config.list(uncalculated=True):
if not tconfig.isdynamic(only_self=True):
# it's not a dynamic variable
continue
identifier = get_identifier_from_dynamic_family(
tconfig.name(), name
)
if identifier != normalize_family(identifier):
msg = _(
'cannot load variable path "{0}", the identifier "{1}" is not valid in {2}'
).format(path, identifier, data["source"])
self.warnings.append(msg)
continue
if identifier is None:
# it's a dynamic variable but doesn't match the current name
continue
# get the variable associate to this dynamic family
dynamic_variable = tconfig.information.get(
"dynamic_variable",
None,
)
if not dynamic_variable:
# it's the good dynamic variable but it's not linked to a variable
# so cannot change the variable
continue
option_type = self.config.option(dynamic_variable).information.get(
"type"
)
dyn_options_values = (
self.config.option(dynamic_variable).get().impl_getdefault()
)
if "{{ identifier }}" in dynamic_variable:
for s in identifiers:
dynamic_variable = dynamic_variable.replace(
"{{ identifier }}", str(s), 1
)
# do not add values in variable if has already a value
if dynamic_variable not in self.values and not dyn_options_values:
self.values[dynamic_variable] = {"values": []}
added.append(dynamic_variable)
elif dynamic_variable not in added:
continue
config = tconfig
identifiers.append(identifier)
typ = CONVERT_OPTION.get(option_type, {}).get("func")
if typ:
identifier = typ(identifier)
if identifier not in self.values[dynamic_variable]["values"]:
self.values[dynamic_variable]["values"].append(identifier)
cache[current_path] = config, identifier
break
def convert_value(self, path, option, options, value):
# converted value
needs_convert = options.get("needs_convert", False)
if option.ismulti():
if options.get("multi_separator") and not isinstance(value, list):
value = value.split(options["multi_separator"])
self.values[path]["values"] = value
if option.issubmulti():
value = [[val] for val in value]
if needs_convert:
if option.issubmulti():
for idx, val in enumerate(value):
value[idx] = [convert_value(option, v) for v in val]
else:
value = [convert_value(option, val) for val in value]
self.values[path]["values"] = value
self.values[path]["options"]["needs_convert"] = False
elif needs_convert:
value = convert_value(option, value)
return value
def _populate_config(self):
while self.values:
value_is_set = False
for option in self._get_variable(self.config):
path = option.path()
if path not in self.values:
continue
if self.only_default and option.owner.get() != owners.default:
self.values.pop(path)
continue
options = self.values[path].get("options", {})
if (
options.get("allow_secrets_variables", True) is False
and option.type() == "password"
):
self.errors.append(
_(
'the variable "{0}" contains secrets and should not be defined in {1}'
).format(path, self.values[path]["source"])
)
continue
value = self.convert_value(
path, option, options, self.values[path]["values"]
)
index = option.index()
if index is not None:
if not isinstance(value, list) or index >= len(value):
continue
value = value[index]
option_without_index = self.config.option(path)
else:
option_without_index = option
if option.isleader():
len_leader = len(option.value.get())
if len_leader:
for idx in range(len_leader - 1, -1, -1):
option.value.pop(idx)
try:
option.value.set(value)
value_is_set = True
except Exception:
if path != option.path():
self.values[option.path()] = self.values.pop(path)
else:
if "source" in self.values[path]:
option_without_index.information.set(
"loaded_from",
_("loaded from {0}").format(self.values[path]["source"]),
)
# value is correctly set, remove variable to the set
if index is not None:
# if it's a follower waiting for all followers are sets
self.values[path]["values"][index] = undefined
for tmp_value in self.values[path]["values"]:
if tmp_value != undefined:
break
else:
self.values.pop(path)
else:
self.values.pop(path)
if not value_is_set:
break
def _display_value(self, option, value):
if not self.show_secrets and option.type() == "password":
return "*" * 10
return value
def _populate_error_warnings(self):
# we don't find variable, apply value just to get error or warning messages
for path, options in self.values.items():
value = options["values"]
option = self.config.option(path)
try:
if option.isoptiondescription():
if value:
self.warnings.append(
_(
'cannot set the value "{0}" to the family {1}, it will be ignored when loading from {2}'
).format(
self._display_value(option, value),
option.description(with_quote=True),
options["source"],
)
)
continue
except AttributeOptionError as err:
if err.code == "option-not-found":
self.warnings.append(
_(
'variable or family "{0}" does not exist, it will be ignored when loading from {1}'
).format(err.path, options["source"])
)
elif err.code == "option-dynamic":
self.warnings.append(
_(
'"{0}" is the name of a dynamic family, it will be ignored when loading from {1}'
).format(option.description(with_quote=True), options["source"])
)
else:
self.warnings.append(
_("{0} loaded from {1}").format(err, options["source"])
)
continue
value = self.convert_value(
path, option, self.values[path].get("options", {}), value
)
if option.isfollower():
indexes = range(len(value))
values = value
else:
indexes = [None]
for index in indexes:
try:
if option.isfollower():
value = values[index]
if value is undefined or isinstance(value, CancelParam):
continue
self.config.option(path, index).value.set(value)
else:
option.value.set(value)
except PropertiesOptionError as err:
if err.code == "property-error":
properties = err.display_properties(
force_property=True, add_quote=False
)
err_path = err._subconfig.path
display_name = option.description(with_quote=True)
if index is not None:
if path == err_path:
self.warnings.append(
_(
'variable {0} at index "{1}" is {2}, it will be ignored when loading from {3}'
).format(
display_name,
index,
properties,
options["source"],
)
)
else:
self.warnings.append(
_(
'family {0} is {1}, {2} at index "{3}" will be ignored when loading from {4}'
).format(
err._name,
properties,
display_name,
index,
options["source"],
)
)
else:
if path == err_path:
self.warnings.append(
_(
"variable {0} is {1}, it will be ignored when loading from {2}"
).format(
display_name, properties, options["source"]
)
)
else:
self.warnings.append(
_(
"family {0} is {1}, {2} will be ignored when loading from {3}"
).format(
err._name,
properties,
display_name,
options["source"],
)
)
else:
self.warnings.append(
_("{0} in {1}").format(err, options["source"])
)
except LeadershipError as err:
self.warnings.append(_("{0} in {1}").format(err, options["source"]))
except ValueError as err:
err.prefix = ""
if index is not None:
self.warnings.append(
_(
'the value "{0}" is invalid for {1} at index "{2}", {3}, it will be ignored when loading from {4}'
).format(
self._display_value(option, value),
option.description(with_quote=True),
index,
err,
options["source"],
)
)
else:
self.warnings.append(
_(
'the value "{0}" is invalid for {1}, {2}, it will be ignored when loading from {3}'
).format(
self._display_value(option, value),
option.description(with_quote=True),
err,
options["source"],
)
)
def convert_value(option, value):
if value == "":
return None
option_type = option.information.get("type")
if option_type == "choice":
choices = option.value.list()
if value not in choices and isinstance(value, str):
# FIXME add other tests (boolean, float, ...)
if value.isnumeric() and int(value) in choices:
value = int(value)
func = CONVERT_OPTION.get(option_type, {}).get("func")
if func:
try:
return func(value)
except:
pass
return value

View file

@ -9,7 +9,7 @@ Cadoles (http://www.cadoles.com)
Copyright (C) 2019-2021
Silique (https://www.silique.fr)
Copyright (C) 2022-2024
Copyright (C) 2022-2025
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the
@ -26,7 +26,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from typing import List, Union
from unicodedata import normalize, combining
import re
from itertools import chain
@ -45,6 +44,8 @@ from .error import DictConsistencyError
NAME_REGEXP = re.compile(r"^[a-z0-9_]*$")
PROPERTY_ATTRIBUTE = ["frozen", "hidden", "disabled", "mandatory"]
def valid_variable_family_name(
name: str,
@ -58,17 +59,6 @@ def valid_variable_family_name(
raise DictConsistencyError(msg, 76, xmlfiles)
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(name, module) -> List[str]:
"""list all functions in a module"""
loader = SourceFileLoader(name, module)
@ -78,13 +68,10 @@ def load_modules(name, module) -> List[str]:
return eosfunc
def get_realpath(
path: str,
path_prefix: str,
) -> str:
if path_prefix:
return f"{path_prefix}.{path}"
return path
# def get_realpath(
# path: str,
# ) -> str:
# return path
def get_jinja_variable_to_param(
@ -93,7 +80,6 @@ def get_jinja_variable_to_param(
objectspace,
xmlfiles,
functions,
path_prefix,
version,
namespace,
):
@ -118,6 +104,8 @@ def get_jinja_variable_to_param(
jinja_text, current_path, err
)
raise DictConsistencyError(msg, 39, xmlfiles) from err
except AttributeError:
pass
variables = list(variables)
variables.sort(reverse=True)
founded_variables = {}
@ -125,7 +113,6 @@ def get_jinja_variable_to_param(
for variable_path in variables:
variable, identifier = objectspace.paths.get_with_dynamic(
variable_path,
path_prefix,
current_path,
version,
namespace,
@ -152,7 +139,6 @@ def get_jinja_variable_to_param(
vpath = vpath.rsplit(".", 1)[0]
variable, identifier = objectspace.paths.get_with_dynamic(
vpath,
path_prefix,
current_path,
version,
namespace,
@ -165,3 +151,60 @@ def get_jinja_variable_to_param(
yield {}, None, root_path
for variable_path, data in founded_variables.items():
yield data[1], data[0], variable_path
def calc_multi_for_type_variable(
local_variable: "Variable",
variable_in_calculation_path: str,
variable_in_calculation: "Variable",
objectspace: "RougailConvert",
) -> Union[bool, str]:
variable_in_calculation_multi = variable_in_calculation.multi
if local_variable.path in objectspace.families:
# it's a family
local_variable_multi = None
else:
local_variable_multi = local_variable.multi
# variable is a leader
if variable_in_calculation.path in objectspace.leaders:
local_variable_parent = local_variable.path.rsplit(".", 1)[0]
variable_in_calculation_parent = variable_in_calculation.path.rsplit(
".", 1
)[0]
if local_variable_parent == variable_in_calculation_parent:
variable_in_calculation_multi = False
# variable is a follower
elif variable_in_calculation.path in objectspace.followers:
local_variable_parent = local_variable.path.rsplit(".", 1)[0]
variable_in_calculation_parent = variable_in_calculation.path.rsplit(
".", 1
)[0]
if local_variable_parent != variable_in_calculation_parent:
if variable_in_calculation_multi:
variable_in_calculation_multi = "submulti"
else:
variable_in_calculation_multi = True
# variable is in a dynamic family
if objectspace.paths.is_dynamic(variable_in_calculation.path):
common_path = get_common_path(
local_variable.path, variable_in_calculation_path
)
common_variable_path = variable_in_calculation_path
if common_path:
common_variable_path = common_variable_path[len(common_path) + 1 :]
count_identifiers = common_variable_path.count("{{ identifier }}")
if count_identifiers == 1:
if variable_in_calculation_multi is False:
variable_in_calculation_multi = True
else:
variable_in_calculation_multi = "submulti"
elif count_identifiers > 1:
variable_in_calculation_multi = "submulti"
return local_variable_multi, variable_in_calculation_multi
class Undefined:
pass
undefined = Undefined()

View file

@ -0,0 +1,8 @@
version: 1.1
file:
type: unix_filename
params:
allow_relative: true
default:
test/unknown_file

View file

@ -0,0 +1 @@
{}

View file

@ -2,7 +2,7 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:

View file

@ -2,7 +2,7 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1 @@
[]

View file

@ -0,0 +1 @@
{}

View file

@ -2,7 +2,7 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
@ -10,6 +10,5 @@ except:
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
optiondescription_1 = OptionDescription(name="1", doc="1", children=[], properties=frozenset({"advanced"}))
optiondescription_2 = OptionDescription(name="2", doc="2", children=[], properties=frozenset({"advanced"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_2])
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[], properties=frozenset({"advanced"}), informations={'ymlfiles': ['']})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,9 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[])

View file

@ -2,7 +2,7 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
@ -10,6 +10,5 @@ except:
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
optiondescription_1 = OptionDescription(name="1", doc="1", children=[], properties=frozenset({"advanced"}))
optiondescription_4 = OptionDescription(name="2", doc="2", children=[], properties=frozenset({"advanced"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_4])
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[], properties=frozenset({"advanced"}), informations={'ymlfiles': ['']})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,9 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[])

View file

@ -2,7 +2,7 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
@ -10,6 +10,5 @@ except:
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
optiondescription_1 = OptionDescription(name="1", doc="1", children=[], properties=frozenset({"advanced"}))
optiondescription_4 = OptionDescription(name="2", doc="2", children=[], properties=frozenset({"advanced"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_4])
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[], properties=frozenset({"advanced"}), informations={'ymlfiles': ['']})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,9 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[])

View file

@ -1,3 +0,0 @@
---
_version: '1.1'
version: # a variable

View file

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

View file

@ -2,7 +2,7 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
@ -10,6 +10,6 @@ except:
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_2 = StrOption(name="version", doc="a variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2], properties=frozenset({"basic"}))
option_2 = StrOption(name="version", doc="a variable", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_0version_underscore/rougail/00-base.yml'], 'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2], properties=frozenset({"basic"}), informations={'ymlfiles': ['']})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -1,19 +0,0 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_3 = StrOption(name="version", doc="a variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
optiondescription_2 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_3], properties=frozenset({"basic"}))
optiondescription_1 = OptionDescription(name="1", doc="1", children=[optiondescription_2], properties=frozenset({"basic"}))
option_6 = StrOption(name="version", doc="a variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
optiondescription_5 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_6], properties=frozenset({"basic"}))
optiondescription_4 = OptionDescription(name="2", doc="2", children=[optiondescription_5], properties=frozenset({"basic"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_4])

View file

@ -2,9 +2,9 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_1 = StrOption(name="version", doc="a variable", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
option_1 = StrOption(name="version", doc="a variable", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_0version_underscore/rougail/00-base.yml'], 'type': 'string'})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1])

View file

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

View file

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

View file

@ -2,7 +2,7 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
@ -10,6 +10,6 @@ except:
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_2 = StrOption(name="empty", doc="empty", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2], properties=frozenset({"basic"}))
option_2 = StrOption(name="empty", doc="empty", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_1empty_variable/rougail/00-base.yml'], 'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2], properties=frozenset({"basic"}), informations={'ymlfiles': ['']})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -1,19 +0,0 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_3 = StrOption(name="empty", doc="empty", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
optiondescription_2 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_3], properties=frozenset({"basic"}))
optiondescription_1 = OptionDescription(name="1", doc="1", children=[optiondescription_2], properties=frozenset({"basic"}))
option_6 = StrOption(name="empty", doc="empty", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
optiondescription_5 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_6], properties=frozenset({"basic"}))
optiondescription_4 = OptionDescription(name="2", doc="2", children=[optiondescription_5], properties=frozenset({"basic"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_4])

View file

@ -2,9 +2,9 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_1 = StrOption(name="empty", doc="empty", properties=frozenset({"basic", "mandatory"}), informations={'type': 'string'})
option_1 = StrOption(name="empty", doc="empty", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_1empty_variable/rougail/00-base.yml'], 'type': 'string'})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1])

View file

@ -1,10 +0,0 @@
---
version: 1.1
var1: "no" # a first variable
var2:
description: a second variable
multi: true
default:
jinja: |
{{ _.var1 }}
description: the value of var1

View file

@ -0,0 +1,6 @@
{
"rougail.var1": "no",
"rougail.var2": [
"no"
]
}

View file

@ -2,7 +2,7 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
@ -11,7 +11,7 @@ ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
dict_env['default_rougail.var2'] = "{{ _.var1 }}\n"
option_2 = StrOption(name="var1", doc="a first variable", default="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_3 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("rougail.var2"), '_.var1': ParamOption(option_2, notraisepropertyerror=True)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3], properties=frozenset({"standard"}))
option_2 = StrOption(name="var1", doc="a first variable", default="no", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated/rougail/00-base.yml'], 'type': 'string'})
option_3 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['../rougail-tests/structures/00_2default_calculated/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("rougail.var2"), '_.var1': ParamOption(option_2, notraisepropertyerror=True)})), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated/rougail/00-base.yml'], 'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3], properties=frozenset({"standard"}), informations={'ymlfiles': ['']})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -1,23 +0,0 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
dict_env['default_1.rougail.var2'] = "{{ _.var1 }}\n"
dict_env['default_2.rougail.var2'] = "{{ _.var1 }}\n"
option_3 = StrOption(name="var1", doc="a first variable", default="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_4 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_1.rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("1.rougail.var2"), '_.var1': ParamOption(option_3, notraisepropertyerror=True)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
optiondescription_2 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_3, option_4], properties=frozenset({"standard"}))
optiondescription_1 = OptionDescription(name="1", doc="1", children=[optiondescription_2], properties=frozenset({"standard"}))
option_7 = StrOption(name="var1", doc="a first variable", default="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_8 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_2.rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("2.rougail.var2"), '_.var1': ParamOption(option_7, notraisepropertyerror=True)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
optiondescription_6 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_7, option_8], properties=frozenset({"standard"}))
optiondescription_5 = OptionDescription(name="2", doc="2", children=[optiondescription_6], properties=frozenset({"standard"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_5])

View file

@ -2,11 +2,11 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
dict_env['default_var2'] = "{{ _.var1 }}\n"
option_1 = StrOption(name="var1", doc="a first variable", default="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_2 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("var2"), '_.var1': ParamOption(option_1, notraisepropertyerror=True)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_1 = StrOption(name="var1", doc="a first variable", default="no", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated/rougail/00-base.yml'], 'type': 'string'})
option_2 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['../rougail-tests/structures/00_2default_calculated/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("var2"), '_.var1': ParamOption(option_1, notraisepropertyerror=True)})), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated/rougail/00-base.yml'], 'type': 'string'})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2])

View file

@ -1,15 +0,0 @@
---
version: 1.1
var1: # a first variable
- 'no'
- 'yes'
- maybe
var2:
description: a second variable
multi: true
default:
jinja: |
{% for val in _.var1 %}
{{ val }}
{% endfor %}
description: the value of _.var1

View file

@ -0,0 +1,12 @@
{
"rougail.var1": [
"no",
"yes",
"maybe"
],
"rougail.var2": [
"no",
"yes",
"maybe"
]
}

View file

@ -2,7 +2,7 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
@ -11,7 +11,7 @@ ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
dict_env['default_rougail.var2'] = "{% for val in _.var1 %}\n{{ val }}\n{% endfor %}\n"
option_2 = StrOption(name="var1", doc="a first variable", multi=True, default=["no", "yes", "maybe"], default_multi="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_3 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated_multi/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("rougail.var2"), '_.var1': ParamOption(option_2, notraisepropertyerror=True)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3], properties=frozenset({"standard"}))
option_2 = StrOption(name="var1", doc="a first variable", multi=True, default=["no", "yes", "maybe"], default_multi="no", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_multi/rougail/00-base.yml'], 'type': 'string'})
option_3 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['../rougail-tests/structures/00_2default_calculated_multi/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("rougail.var2"), '_.var1': ParamOption(option_2, notraisepropertyerror=True)})), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_multi/rougail/00-base.yml'], 'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3], properties=frozenset({"standard"}), informations={'ymlfiles': ['']})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -1,23 +0,0 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
dict_env['default_1.rougail.var2'] = "{% for val in _.var1 %}\n{{ val }}\n{% endfor %}\n"
dict_env['default_2.rougail.var2'] = "{% for val in _.var1 %}\n{{ val }}\n{% endfor %}\n"
option_3 = StrOption(name="var1", doc="a first variable", multi=True, default=["no", "yes", "maybe"], default_multi="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_4 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_1.rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated_multi/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("1.rougail.var2"), '_.var1': ParamOption(option_3, notraisepropertyerror=True)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
optiondescription_2 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_3, option_4], properties=frozenset({"standard"}))
optiondescription_1 = OptionDescription(name="1", doc="1", children=[optiondescription_2], properties=frozenset({"standard"}))
option_7 = StrOption(name="var1", doc="a first variable", multi=True, default=["no", "yes", "maybe"], default_multi="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_8 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_2.rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated_multi/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("2.rougail.var2"), '_.var1': ParamOption(option_7, notraisepropertyerror=True)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
optiondescription_6 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_7, option_8], properties=frozenset({"standard"}))
optiondescription_5 = OptionDescription(name="2", doc="2", children=[optiondescription_6], properties=frozenset({"standard"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_5])

View file

@ -2,11 +2,11 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
dict_env['default_var2'] = "{% for val in _.var1 %}\n{{ val }}\n{% endfor %}\n"
option_1 = StrOption(name="var1", doc="a first variable", multi=True, default=["no", "yes", "maybe"], default_multi="no", properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_2 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['tests/dictionaries/00_2default_calculated_multi/dictionaries/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("var2"), '_.var1': ParamOption(option_1, notraisepropertyerror=True)})), properties=frozenset({"mandatory", "standard"}), informations={'type': 'string'})
option_1 = StrOption(name="var1", doc="a first variable", multi=True, default=["no", "yes", "maybe"], default_multi="no", properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_multi/rougail/00-base.yml'], 'type': 'string'})
option_2 = StrOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(True), '__internal_files': ParamValue(['../rougail-tests/structures/00_2default_calculated_multi/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("var2"), '_.var1': ParamOption(option_1, notraisepropertyerror=True)})), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_multi/rougail/00-base.yml'], 'type': 'string'})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2])

View file

@ -0,0 +1,20 @@
{
"rougail.leadership.var1": {
"owner": "default",
"value": [
"a_value"
]
},
"rougail.leadership.var2": {
"owner": [
"default"
],
"value": [
"a_value"
]
},
"rougail.var2": {
"owner": "default",
"value": "a_value"
}
}

View file

@ -0,0 +1,9 @@
{
"rougail.leadership.var1": [
{
"rougail.leadership.var1": "a_value",
"rougail.leadership.var2": "a_value"
}
],
"rougail.var2": "a_value"
}

View file

@ -0,0 +1,20 @@
{
"rougail.leadership.var1": {
"owner": "default",
"value": [
"a_value"
]
},
"rougail.leadership.var2": {
"owner": [
"default"
],
"value": [
"a_value"
]
},
"rougail.var2": {
"owner": "default",
"value": "a_value"
}
}

View file

@ -0,0 +1,3 @@
{
"rougail.var2": "a_value"
}

View file

@ -0,0 +1,19 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
dict_env['default_rougail.var2'] = "{{ var1[0] }}"
option_3 = StrOption(name="var1", doc="a first variable", multi=True, default=["a_value"], properties=frozenset({"force_default_on_freeze", "frozen", "mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_params_permissive/rougail/00-base.yml'], 'type': 'string'})
option_4 = StrOption(name="var2", doc="a first variable", multi=True, default_multi="a_value", properties=frozenset({"force_default_on_freeze", "frozen", "mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_params_permissive/rougail/00-base.yml'], 'type': 'string'})
optiondescription_2 = Leadership(name="leadership", doc="leadership", children=[option_3, option_4], properties=frozenset({"hidden", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_params_permissive/rougail/00-base.yml']})
option_5 = StrOption(name="var2", doc="a second variable", default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_rougail.var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(False), '__internal_files': ParamValue(['../rougail-tests/structures/00_2default_calculated_params_permissive/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("rougail.var2"), 'var1': ParamOption(option_3)})), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_params_permissive/rougail/00-base.yml'], 'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[optiondescription_2, option_5], properties=frozenset({"standard"}), informations={'ymlfiles': ['']})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,14 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
dict_env['default_var2'] = "{{ var1[0] }}"
option_2 = StrOption(name="var1", doc="a first variable", multi=True, default=["a_value"], properties=frozenset({"force_default_on_freeze", "frozen", "mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_params_permissive/rougail/00-base.yml'], 'type': 'string'})
option_3 = StrOption(name="var2", doc="a first variable", multi=True, default_multi="a_value", properties=frozenset({"force_default_on_freeze", "frozen", "mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_params_permissive/rougail/00-base.yml'], 'type': 'string'})
optiondescription_1 = Leadership(name="leadership", doc="leadership", children=[option_2, option_3], properties=frozenset({"hidden", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_params_permissive/rougail/00-base.yml']})
option_4 = StrOption(name="var2", doc="a second variable", default=Calculation(func['jinja_to_function'], Params((), kwargs={'__internal_jinja': ParamValue("default_var2"), '__internal_type': ParamValue("string"), '__internal_multi': ParamValue(False), '__internal_files': ParamValue(['../rougail-tests/structures/00_2default_calculated_params_permissive/rougail/00-base.yml']), '__internal_attribute': ParamValue("default"), '__internal_variable': ParamValue("var2"), 'var1': ParamOption(option_2)})), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_params_permissive/rougail/00-base.yml'], 'type': 'string'})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, option_4])

View file

@ -0,0 +1,10 @@
{
"rougail.var1": {
"owner": "default",
"value": null
},
"rougail.var2": {
"owner": "default",
"value": null
}
}

View file

@ -0,0 +1,4 @@
{
"rougail.var1": null,
"rougail.var2": null
}

View file

@ -0,0 +1,10 @@
{
"rougail.var1": {
"owner": "default",
"value": null
},
"rougail.var2": {
"owner": "default",
"value": null
}
}

View file

@ -0,0 +1 @@
["rougail.var1", "rougail.var2"]

View file

@ -0,0 +1,4 @@
{
"rougail.var1": null,
"rougail.var2": null
}

View file

@ -0,0 +1,16 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_2 = StrOption(name="var1", doc="a first variable", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description/rougail/00-base.yml'], 'type': 'string'})
option_3 = StrOption(name="var2", doc="a second variable", default=Calculation(func['calc_value'], Params((ParamOption(option_2)))), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description/rougail/00-base.yml'], 'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3], properties=frozenset({"basic"}), informations={'ymlfiles': ['']})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,11 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_1 = StrOption(name="var1", doc="a first variable", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description/rougail/00-base.yml'], 'type': 'string'})
option_2 = StrOption(name="var2", doc="a second variable", default=Calculation(func['calc_value'], Params((ParamOption(option_1)))), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description/rougail/00-base.yml'], 'type': 'string'})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2])

View file

@ -0,0 +1,17 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_2 = StrOption(name="var1", doc="a first variable", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description_multi_line/rougail/00-base.yml'], 'type': 'string'})
option_3 = StrOption(name="var2", doc="a second variable", default=Calculation(func['calc_value'], Params((ParamOption(option_2)))), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description_multi_line/rougail/00-base.yml'], 'type': 'string'})
option_4 = StrOption(name="var3", doc="a new variable", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description_multi_line/rougail/00-base.yml'], 'type': 'string'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3, option_4], properties=frozenset({"basic"}), informations={'ymlfiles': ['']})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -0,0 +1,12 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_1 = StrOption(name="var1", doc="a first variable", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description_multi_line/rougail/00-base.yml'], 'type': 'string'})
option_2 = StrOption(name="var2", doc="a second variable", default=Calculation(func['calc_value'], Params((ParamOption(option_1)))), properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description_multi_line/rougail/00-base.yml'], 'type': 'string'})
option_3 = StrOption(name="var3", doc="a new variable", properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_description_multi_line/rougail/00-base.yml'], 'type': 'string'})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2, option_3])

View file

@ -1,15 +0,0 @@
---
version: 1.1
var1:
description: a first variable
multi: true
type: domainname
params:
allow_ip: true
var2:
description: a second variable
default:
type: variable
variable: _.var1

View file

@ -0,0 +1,4 @@
{
"rougail.var1": [],
"rougail.var2": []
}

View file

@ -2,7 +2,7 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
try:
groups.namespace
except:
@ -10,7 +10,7 @@ except:
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_2 = DomainnameOption(name="var1", doc="a first variable", multi=True, type="domainname", allow_ip=True, properties=frozenset({"basic", "mandatory"}), informations={'type': 'domainname'})
option_3 = DomainnameOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['calc_value'], Params((ParamOption(option_2)))), type="domainname", allow_ip=True, properties=frozenset({"mandatory", "standard"}), informations={'type': 'domainname'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3], properties=frozenset({"basic"}))
option_2 = DomainnameOption(name="var1", doc="a first variable", multi=True, type="domainname", allow_ip=True, properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_transitive/rougail/00-base.yml'], 'type': 'domainname'})
option_3 = DomainnameOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['calc_value'], Params((ParamOption(option_2)), kwargs={'__internal_multi': ParamValue(True)})), type="domainname", allow_ip=True, properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_transitive/rougail/00-base.yml'], 'type': 'domainname'})
optiondescription_1 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_2, option_3], properties=frozenset({"basic"}), informations={'ymlfiles': ['']})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1])

View file

@ -1,21 +0,0 @@
from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
try:
groups.namespace
except:
groups.addgroup('namespace')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_3 = DomainnameOption(name="var1", doc="a first variable", multi=True, type="domainname", allow_ip=True, properties=frozenset({"basic", "mandatory"}), informations={'type': 'domainname'})
option_4 = DomainnameOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['calc_value'], Params((ParamOption(option_3)))), type="domainname", allow_ip=True, properties=frozenset({"mandatory", "standard"}), informations={'type': 'domainname'})
optiondescription_2 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_3, option_4], properties=frozenset({"basic"}))
optiondescription_1 = OptionDescription(name="1", doc="1", children=[optiondescription_2], properties=frozenset({"basic"}))
option_7 = DomainnameOption(name="var1", doc="a first variable", multi=True, type="domainname", allow_ip=True, properties=frozenset({"basic", "mandatory"}), informations={'type': 'domainname'})
option_8 = DomainnameOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['calc_value'], Params((ParamOption(option_7)))), type="domainname", allow_ip=True, properties=frozenset({"mandatory", "standard"}), informations={'type': 'domainname'})
optiondescription_6 = OptionDescription(name="rougail", doc="Rougail", group_type=groups.namespace, children=[option_7, option_8], properties=frozenset({"basic"}))
optiondescription_5 = OptionDescription(name="2", doc="2", children=[optiondescription_6], properties=frozenset({"basic"}))
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[optiondescription_1, optiondescription_5])

View file

@ -2,10 +2,10 @@ from tiramisu import *
from tiramisu.setting import ALLOWED_LEADER_PROPERTIES
from re import compile as re_compile
from rougail.tiramisu import func, dict_env, load_functions, ConvertDynOptionDescription
load_functions('tests/dictionaries/../eosfunc/test.py')
load_functions('../rougail-tests/funcs/test.py')
ALLOWED_LEADER_PROPERTIES.add("basic")
ALLOWED_LEADER_PROPERTIES.add("standard")
ALLOWED_LEADER_PROPERTIES.add("advanced")
option_1 = DomainnameOption(name="var1", doc="a first variable", multi=True, type="domainname", allow_ip=True, properties=frozenset({"basic", "mandatory"}), informations={'type': 'domainname'})
option_2 = DomainnameOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['calc_value'], Params((ParamOption(option_1)))), type="domainname", allow_ip=True, properties=frozenset({"mandatory", "standard"}), informations={'type': 'domainname'})
option_1 = DomainnameOption(name="var1", doc="a first variable", multi=True, type="domainname", allow_ip=True, properties=frozenset({"basic", "mandatory"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_transitive/rougail/00-base.yml'], 'type': 'domainname'})
option_2 = DomainnameOption(name="var2", doc="a second variable", multi=True, default=Calculation(func['calc_value'], Params((ParamOption(option_1)), kwargs={'__internal_multi': ParamValue(True)})), type="domainname", allow_ip=True, properties=frozenset({"mandatory", "standard"}), informations={'ymlfiles': ['../rougail-tests/structures/00_2default_calculated_variable_transitive/rougail/00-base.yml'], 'type': 'domainname'})
option_0 = OptionDescription(name="baseoption", doc="baseoption", children=[option_1, option_2])

View file

@ -1,4 +0,0 @@
---
version: '1.0'
var1:
description: a variable

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