import tiramisu-json in tiramisu repository

This commit is contained in:
2019-04-08 08:41:33 +02:00
parent 48b3b7e8a4
commit 1aec891408
176 changed files with 5878 additions and 202 deletions

View File

@ -25,6 +25,7 @@ from .setting import ConfigBag, OptionBag, owners, groups, Undefined, undefined,
FORBIDDEN_SET_PROPERTIES, SPECIAL_PROPERTIES, EXPIRATION_TIME
from .config import KernelConfig, SubConfig, KernelGroupConfig, KernelMetaConfig, KernelMixConfig
from .option import ChoiceOption, OptionDescription
from .todict import TiramisuDict
TIRAMISU_VERSION = 3
@ -626,6 +627,7 @@ class _TiramisuOption(CommonTiramisu):
self._option_bag.path = self._path
self._option_bag.index = self._index
self._option_bag.config_bag = self._config_bag
self._tiramisu_dict = None
if not self._registers:
_registers(self._registers, 'TiramisuOption')
@ -728,6 +730,29 @@ class _TiramisuOptionDescription(_TiramisuOption):
subconfig,
self._config_bag)
def dict(self,
clearable: str="all",
remotable: str="minimum",
form: List=[]) -> Dict:
"""convert config and option to tiramisu format"""
if self._tiramisu_dict is None:
option = self._get_option()
name = option.impl_getname()
root = self._subconfig._get_subpath(name)
config = self._config_bag.context
self._tiramisu_dict = TiramisuDict(Config(self._config_bag.context),
root=root,
clearable=clearable,
remotable=remotable)
return self._tiramisu_dict.todict(form)
def updates(self,
body: List) -> Dict:
"""updates value with tiramisu format"""
if self._tiramisu_dict is None:
raise APIError(_('please use .dict() before .updates()'))
return self._tiramisu_dict.set_updates(body)
class TiramisuOption(CommonTiramisuOption):
"""Manage selected option"""
@ -1060,6 +1085,11 @@ class TiramisuContextPermissive(TiramisuContext):
class TiramisuContextOption(TiramisuContext):
def __init__(self,
*args,
**kwargs) -> None:
self._tiramisu_dict = None
super().__init__(*args, **kwargs)
def _find(self,
name,
@ -1159,6 +1189,25 @@ class TiramisuContextOption(TiramisuContext):
config_bag.context):
yield toption
def dict(self,
clearable="all",
remotable="minimum",
form=[]):
"""convert config and option to tiramisu format"""
if self._tiramisu_dict is None:
self._tiramisu_dict = TiramisuDict(Config(self._config_bag.context),
root=None,
clearable=clearable,
remotable=remotable)
return self._tiramisu_dict.todict(form)
def updates(self,
body: List) -> Dict:
"""updates value with tiramisu format"""
if self._tiramisu_dict is None:
raise APIError(_('please use .dict() before .updates()'))
return self._tiramisu_dict.set_updates(body)
class _TiramisuContextConfigReset():
def reset(self):

View File

@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Tiramisu\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-05 08:46+CET\n"
"POT-Creation-Date: 2019-04-08 08:41+CEST\n"
"PO-Revision-Date: \n"
"Last-Translator: Emmanuel Garette <egarette@cadoles.com>\n"
"Language-Team: Tiramisu's team <egarette@cadoles.com>\n"
@ -14,74 +14,78 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Poedit-SourceCharset: UTF-8\n"
#: tiramisu/api.py:58
#: tiramisu/api.py:59
msgid "Settings:"
msgstr "Paramètres :"
#: tiramisu/api.py:59
#: tiramisu/api.py:60
msgid "Access to option without verifying permissive properties"
msgstr "Accès à une option sans vérifié les propriétés permises"
#: tiramisu/api.py:60
#: tiramisu/api.py:61
msgid "Access to option without property restriction"
msgstr "Accès à une option sans restriction de propriété"
#: tiramisu/api.py:64
#: tiramisu/api.py:65
msgid "Call: {}"
msgstr "Appel : {}"
#: tiramisu/api.py:66
#: tiramisu/api.py:67
msgid "Commands:"
msgstr "Commandes :"
#: tiramisu/api.py:103
#: tiramisu/api.py:104
msgid ""
"index \"{}\" is higher than the leadership length \"{}\" for option \"{}\""
msgstr ""
"l'index \"{}\" est supérieur à la longueur de l'option leadership \"{}\" "
"pour l'option \"{}\""
#: tiramisu/api.py:108
#: tiramisu/api.py:109
msgid "option must not be an optiondescription"
msgstr "option ne doit pas être une optiondescription"
#: tiramisu/api.py:135
#: tiramisu/api.py:136
msgid "index must be set with the follower option \"{}\""
msgstr "l'index est obligatoire pour l'option suiveuse \"{}\""
#: tiramisu/api.py:138
#: tiramisu/api.py:139
msgid "unknown method {}"
msgstr "méthode {} inconnue"
#: tiramisu/api.py:360
#: tiramisu/api.py:361
msgid "cannot add this property: \"{0}\""
msgstr "ne peut pas ajouter cette propriété : \"{0}\""
#: tiramisu/api.py:503 tiramisu/config.py:252
#: tiramisu/api.py:505 tiramisu/config.py:252
msgid "can't delete a SymLinkOption"
msgstr "ne peut supprimer une valeur à une SymLinkOption"
#: tiramisu/api.py:636 tiramisu/api.py:1322
#: tiramisu/api.py:639 tiramisu/api.py:1375
msgid "please specify a valid sub function ({})"
msgstr "veuillez spécifier une sous fonction valide ({})"
#: tiramisu/api.py:699 tiramisu/api.py:1145
#: tiramisu/api.py:702 tiramisu/api.py:1176
msgid "unknown list type {}"
msgstr "type de liste inconnue {}"
#: tiramisu/api.py:701 tiramisu/api.py:1147
#: tiramisu/api.py:704 tiramisu/api.py:1178
msgid "unknown group_type: {0}"
msgstr "group_type inconnu: {0}"
#: tiramisu/api.py:974
#: tiramisu/api.py:753 tiramisu/api.py:1208
msgid "please use .dict() before .updates()"
msgstr "faire .dico() avant .updates()"
#: tiramisu/api.py:1000
msgid "properties must be a set"
msgstr "une propriété doit être de type set"
#: tiramisu/api.py:980 tiramisu/api.py:1002
#: tiramisu/api.py:1006 tiramisu/api.py:1028
msgid "unknown when {} (must be in append or remove)"
msgstr "value {} inconsistent (doit être append ou remove)"
#: tiramisu/api.py:992 tiramisu/api.py:1014 tiramisu/config.py:1227
#: tiramisu/api.py:1018 tiramisu/api.py:1040 tiramisu/config.py:1249
msgid "unknown type {}"
msgstr "type inconnu {}"
@ -191,9 +195,9 @@ msgstr "le nom d'un config doit être unique dans un groupconfig pour \"{0}\""
msgid "unknown config \"{}\""
msgstr "config \"{}\" inconnue"
#: tiramisu/config.py:987 tiramisu/config.py:1202
#: tiramisu/config.py:987 tiramisu/config.py:1224
msgid "{}config's children should be config, not {}"
msgstr "enfants d'un {}config doit être une config, pas {0}"
msgstr "enfants d'un {}config doit être une config, pas {}"
#: tiramisu/config.py:992
msgid "child must be a Config, MixConfig or MetaConfig"
@ -216,27 +220,38 @@ msgid "force_default and force_dont_change_value cannot be set together"
msgstr ""
"force_default et force_dont_change_value ne peuvent pas être mis ensemble"
#: tiramisu/config.py:1189
#: tiramisu/config.py:1176
msgid "config is already in a metaconfig"
msgstr "la config est déjà dans une metaconfig"
#: tiramisu/config.py:1178 tiramisu/config.py:1247
msgid "config name must be uniq in groupconfig for {0}"
msgstr "le nom de la config doit être unique dans un groupconfig pour {0}"
#: tiramisu/config.py:1192
msgid "cannot find the config {}"
msgstr "ne peut pas trouver la config {0}"
#: tiramisu/config.py:1211
msgid "MetaConfig with optiondescription must have string has child, not {}"
msgstr ""
"MetaConfig avec une optiondescription doit avoir un nom comme enfant, pas {}"
#: tiramisu/config.py:1208
#: tiramisu/config.py:1230
msgid "child must be a Config or MetaConfig"
msgstr "enfant doit être une une Config ou une MetaConfig"
#: tiramisu/config.py:1212
#: tiramisu/config.py:1234
msgid "all config in metaconfig must have the same optiondescription"
msgstr ""
"toutes les configs d'une metaconfig doivent avoir la même optiondescription"
#: tiramisu/config.py:1225
msgid "config name must be uniq in groupconfig for {0}"
msgstr "le nom de la config doit être unique dans un groupconfig pour {0}"
#: tiramisu/config.py:1250
msgid "cannot find the config {}"
msgstr "ne peut pas trouver la config {0}"
#: tiramisu/config.py:1272
#, fuzzy
#| msgid "all config in metaconfig must have the same optiondescription"
msgid "metaconfig must have the same optiondescription"
msgstr ""
"toutes les configs d'une metaconfig doivent avoir la même optiondescription"
#: tiramisu/error.py:24
msgid "and"
@ -250,11 +265,11 @@ msgstr "ou"
msgid " {} "
msgstr " {} "
#: tiramisu/error.py:103 tiramisu/setting.py:563
#: tiramisu/error.py:103 tiramisu/setting.py:579
msgid "property"
msgstr "de la propriété"
#: tiramisu/error.py:105 tiramisu/setting.py:565
#: tiramisu/error.py:105 tiramisu/setting.py:581
msgid "properties"
msgstr "des propriétés"
@ -266,38 +281,42 @@ msgstr "ne peut accéder à {0} \"{1}\" parce que \"{2}\" a {3} {4}"
msgid "cannot access to {0} \"{1}\" because has {2} {3}"
msgstr "ne peut accéder à l'{0} \"{1}\" a cause {2} {3}"
#: tiramisu/error.py:185
#: tiramisu/error.py:189
msgid "invalid value"
msgstr "valeur invalide"
#: tiramisu/error.py:190
#: tiramisu/error.py:194
msgid "attention, \"{0}\" could be an invalid {1} for \"{2}\""
msgstr "attention, \"{0}\" peut être un {1} invalide pour \"{2}\""
#: tiramisu/error.py:194 tiramisu/error.py:198
#: tiramisu/error.py:198 tiramisu/error.py:202
msgid "\"{0}\" is an invalid {1} for \"{2}\""
msgstr "\"{0}\" est une valeur invalide pour l'option \"{2}\" de type {1}"
#: tiramisu/function.py:31
#: tiramisu/function.py:34
msgid "args in params must be a tuple"
msgstr "args dans params doit être un tuple"
#: tiramisu/function.py:34 tiramisu/function.py:39
#: tiramisu/function.py:37 tiramisu/function.py:42
msgid "arg in params must be a Param"
msgstr "arg dans params doit être un Param"
#: tiramisu/function.py:36
#: tiramisu/function.py:39
msgid "kwargs in params must be a dict"
msgstr "kwargs dans params doit être un dict"
#: tiramisu/function.py:52
#: tiramisu/function.py:58
msgid "paramoption needs an option not {}"
msgstr "paramoption doit être une option pas {0}"
#: tiramisu/function.py:58
#: tiramisu/function.py:64
msgid "param must have a boolean not a {} for notraisepropertyerror"
msgstr "param doit avoir un booléan pas un {} pour notraisepropertyerror"
#: tiramisu/function.py:271
msgid "unexpected {} condition_operator in calc_value"
msgstr "condition_operator {} inattendue dans la fonction calc_value"
#: tiramisu/option/baseoption.py:75 tiramisu/option/symlinkoption.py:33
msgid "\"{0}\" is an invalid name for an option"
msgstr "\"{0}\" est un nom invalide pour une option"
@ -328,7 +347,7 @@ msgstr ""
msgid "missing those arguments \"{}\" in function \"{}\" for \"{}\""
msgstr "les arguments \"{}\" sont manquant dans la fonction \"{}\" pour \"{}\""
#: tiramisu/option/baseoption.py:257
#: tiramisu/option/baseoption.py:258
msgid ""
"params defined for a callback function but no callback defined yet for "
"option \"{0}\""
@ -336,28 +355,28 @@ msgstr ""
"paramètres définis pour la fonction de callback mais aucun callback défini "
"pour l'option \"{0}\""
#: tiramisu/option/baseoption.py:349 tiramisu/storage/dictionary/value.py:275
#: tiramisu/option/baseoption.py:350 tiramisu/storage/dictionary/value.py:275
#: tiramisu/storage/sqlite3/value.py:201
msgid "information's item not found: {0}"
msgstr "item '{0}' dans les informations non trouvée"
#: tiramisu/option/baseoption.py:362
#: tiramisu/option/baseoption.py:363
msgid "'{0}' ({1}) object attribute '{2}' is read-only"
msgstr "l'attribut {2} de l'objet '{0}' ({1}) est en lecture seule"
#: tiramisu/option/baseoption.py:393
#: tiramisu/option/baseoption.py:394
msgid "\"{}\" ({}) object attribute \"{}\" is read-only"
msgstr "\"{}\" ({}) l'attribut de l'objet \"{}\" est en lecture seule"
#: tiramisu/option/baseoption.py:403
#: tiramisu/option/baseoption.py:404
msgid "\"{}\" not part of any Config"
msgstr "\"{}\" ne fait pas parti d'une Config"
#: tiramisu/option/baseoption.py:450
#: tiramisu/option/baseoption.py:453
msgid "malformed requirements must be an option in option {0}"
msgstr "requirements mal formés doit être une option dans l'option {0}"
#: tiramisu/option/baseoption.py:453
#: tiramisu/option/baseoption.py:456
msgid ""
"malformed requirements multi option must not set as requires of non multi "
"option {0}"
@ -365,59 +384,59 @@ msgstr ""
"requirements mal formés une option multiple ne doit pas être spécifié comme "
"pré-requis à l'option non multiple {0}"
#: tiramisu/option/baseoption.py:486
#: tiramisu/option/baseoption.py:495
msgid ""
"malformed requirements expected must have option and value for option {0}"
msgstr ""
"expected mal formés pour le requirements, doit avoir une option et une "
"valeur pour l'option {0}"
#: tiramisu/option/baseoption.py:493 tiramisu/option/baseoption.py:509
#: tiramisu/option/baseoption.py:502 tiramisu/option/baseoption.py:518
msgid "malformed requirements expected value must be valid for option {0}: {1}"
msgstr ""
"valeur de \"expected\" malformé, doit être valide pour l'option {0} : {1}"
#: tiramisu/option/baseoption.py:523
#: tiramisu/option/baseoption.py:532
msgid ""
"malformed requirements for option: {0} action cannot be force_store_value"
msgstr ""
"requirements mal formés pour l'option : {0} action ne peut pas être "
"force_store_value"
#: tiramisu/option/baseoption.py:531
#: tiramisu/option/baseoption.py:540
msgid "malformed requirements for option: {0} inverse must be boolean"
msgstr ""
"requirements mal formés pour l'option : {0} inverse doit être un booléen"
#: tiramisu/option/baseoption.py:538
#: tiramisu/option/baseoption.py:547
msgid "malformed requirements for option: {0} transitive must be boolean"
msgstr ""
"requirements mal formés pour l'option : {0} transitive doit être booléen"
#: tiramisu/option/baseoption.py:545
#: tiramisu/option/baseoption.py:554
msgid "malformed requirements for option: {0} same_action must be boolean"
msgstr ""
"requirements mal formés pour l'option : {0} same_action doit être un booléen"
#: tiramisu/option/baseoption.py:552
#: tiramisu/option/baseoption.py:561
msgid ""
"malformed requirements for option: \"{0}\" operator must be \"or\" or \"and\""
msgstr ""
"requirements mal formés pour l'option : \"{0}\" l'opérateur doit être \"or\" "
"ou \"and\""
#: tiramisu/option/baseoption.py:564
#: tiramisu/option/baseoption.py:574
msgid "malformed requirements type for option: {0}, must be a dict"
msgstr ""
"type requirements malformé pour l'option : {0}, doit être un dictionnaire"
#: tiramisu/option/baseoption.py:570
#: tiramisu/option/baseoption.py:580
msgid "malformed requirements for option: {0} unknown keys {1}, must only {2}"
msgstr ""
"requirements mal formés pour l'option : {0} clefs inconnues {1}, doit "
"seulement avoir {2}"
#: tiramisu/option/baseoption.py:579
#: tiramisu/option/baseoption.py:592
msgid ""
"malformed requirements for option: {0} require must have option, expected "
"and action keys"
@ -425,56 +444,56 @@ msgstr ""
"requirements malformé pour l'option : {0} l'exigence doit avoir les clefs "
"option, expected et action"
#: tiramisu/option/booloption.py:30
#: tiramisu/option/booloption.py:31
msgid "boolean"
msgstr "booléen"
#: tiramisu/option/broadcastoption.py:31
#: tiramisu/option/broadcastoption.py:32
msgid "broadcast address"
msgstr "adresse broadcast"
#: tiramisu/option/broadcastoption.py:38 tiramisu/option/dateoption.py:37
#: tiramisu/option/domainnameoption.py:113 tiramisu/option/ipoption.py:77
#: tiramisu/option/netmaskoption.py:41 tiramisu/option/networkoption.py:67
#: tiramisu/option/passwordoption.py:38 tiramisu/option/portoption.py:106
#: tiramisu/option/urloption.py:40
#: tiramisu/option/broadcastoption.py:39 tiramisu/option/dateoption.py:38
#: tiramisu/option/domainnameoption.py:118 tiramisu/option/ipoption.py:83
#: tiramisu/option/netmaskoption.py:42 tiramisu/option/networkoption.py:68
#: tiramisu/option/passwordoption.py:39 tiramisu/option/portoption.py:107
#: tiramisu/option/urloption.py:41
msgid "invalid string"
msgstr "invalide caractère"
#: tiramisu/option/broadcastoption.py:56
#: tiramisu/option/broadcastoption.py:57
msgid "invalid len for vals"
msgstr "longueur invalide pour vals"
#: tiramisu/option/broadcastoption.py:61
#: tiramisu/option/broadcastoption.py:62
msgid "broadcast \"{4}\" invalid with network {0}/{1} (\"{2}\"/\"{3}\")"
msgstr ""
"le broadcast \"{4}\" est invalide pour le réseau {0}/{1} (\"{2}\"/\"{3}\")"
#: tiramisu/option/choiceoption.py:36
#: tiramisu/option/choiceoption.py:37
msgid "choice"
msgstr "choix"
#: tiramisu/option/choiceoption.py:65
#: tiramisu/option/choiceoption.py:66
msgid "values is not a function, so values_params must be None"
msgstr "values n'est pas une fonction, donc values_params doit être None"
#: tiramisu/option/choiceoption.py:67
#: tiramisu/option/choiceoption.py:68
msgid "values must be a tuple or a function for {0}"
msgstr "values doit être un tuple ou une fonction pour {0}"
#: tiramisu/option/choiceoption.py:100
#: tiramisu/option/choiceoption.py:101
msgid "calculated values for {0} is not a list"
msgstr "valeurs calculées for {0} n'est pas une liste"
#: tiramisu/option/choiceoption.py:113
#: tiramisu/option/choiceoption.py:114
msgid "only \"{0}\" is allowed"
msgstr "seul \"{0}\" est autorisé"
#: tiramisu/option/choiceoption.py:116
#: tiramisu/option/choiceoption.py:117
msgid "only \"{0}\" are allowed"
msgstr "seul \"{0}\" sont autorisés"
#: tiramisu/option/dateoption.py:30
#: tiramisu/option/dateoption.py:31
msgid "date"
msgstr "date"
@ -482,43 +501,43 @@ msgstr "date"
msgid "domain name"
msgstr "nom de domaine"
#: tiramisu/option/domainnameoption.py:58
#: tiramisu/option/domainnameoption.py:59
msgid "unknown type_ {0} for hostname"
msgstr "type_ inconnu {0} pour le nom d'hôte"
#: tiramisu/option/domainnameoption.py:61
#: tiramisu/option/domainnameoption.py:62
msgid "allow_ip must be a boolean"
msgstr "allow_ip doit être un booléen"
#: tiramisu/option/domainnameoption.py:63
#: tiramisu/option/domainnameoption.py:64
msgid "allow_without_dot must be a boolean"
msgstr "allow_without_dot doit être un booléen"
#: tiramisu/option/domainnameoption.py:107
#: tiramisu/option/domainnameoption.py:112
msgid "invalid length (min 1)"
msgstr "longueur invalide (min 1)"
#: tiramisu/option/domainnameoption.py:109
#: tiramisu/option/domainnameoption.py:114
msgid "invalid length (max {0})"
msgstr "longueur invalide (max {0})"
#: tiramisu/option/domainnameoption.py:121
#: tiramisu/option/domainnameoption.py:125
msgid "must not be an IP"
msgstr "ne doit pas être une IP"
#: tiramisu/option/domainnameoption.py:125
#: tiramisu/option/domainnameoption.py:131
msgid "must have dot"
msgstr "doit avec un point"
#: tiramisu/option/domainnameoption.py:127
#: tiramisu/option/domainnameoption.py:133
msgid "invalid length (max 255)"
msgstr "longueur invalide (max 255)"
#: tiramisu/option/domainnameoption.py:135
#: tiramisu/option/domainnameoption.py:141
msgid "some characters are uppercase"
msgstr "des caractères sont en majuscule"
#: tiramisu/option/domainnameoption.py:138
#: tiramisu/option/domainnameoption.py:144
msgid "some characters may cause problems"
msgstr "des caractères peuvent poser problèmes"
@ -550,63 +569,63 @@ msgstr ""
"le callback de DynOptionDescription retourne une list avec plusieurs valeurs "
"\"{}\""
#: tiramisu/option/emailoption.py:31
#: tiramisu/option/emailoption.py:32
msgid "email address"
msgstr "adresse mail"
#: tiramisu/option/filenameoption.py:30
#: tiramisu/option/filenameoption.py:31
msgid "file name"
msgstr "nom de fichier"
#: tiramisu/option/floatoption.py:30
#: tiramisu/option/floatoption.py:31
msgid "float"
msgstr "nombre flottant"
#: tiramisu/option/intoption.py:30
#: tiramisu/option/intoption.py:31
msgid "integer"
msgstr "nombre"
#: tiramisu/option/intoption.py:54
#: tiramisu/option/intoption.py:55
msgid "value must be greater than \"{0}\""
msgstr "valeur doit être supérieur à {0}"
#: tiramisu/option/intoption.py:57
#: tiramisu/option/intoption.py:58
msgid "value must be less than \"{0}\""
msgstr "valeur doit être inférieur à \"{0}\""
#: tiramisu/option/ipoption.py:35
#: tiramisu/option/ipoption.py:36
msgid "IP"
msgstr "IP"
#: tiramisu/option/ipoption.py:83 tiramisu/option/networkoption.py:73
#: tiramisu/option/ipoption.py:89 tiramisu/option/networkoption.py:74
msgid "must use CIDR notation"
msgstr "doit utiliser la notation CIDR"
#: tiramisu/option/ipoption.py:105
#: tiramisu/option/ipoption.py:111
msgid "shouldn't be reserved IP"
msgstr "ne devrait pas être une IP réservée"
#: tiramisu/option/ipoption.py:107
#: tiramisu/option/ipoption.py:113
msgid "mustn't be reserved IP"
msgstr "ne doit pas être une IP réservée"
#: tiramisu/option/ipoption.py:111
#: tiramisu/option/ipoption.py:117
msgid "should be private IP"
msgstr "devrait être une IP privée"
#: tiramisu/option/ipoption.py:113
#: tiramisu/option/ipoption.py:119
msgid "must be private IP"
msgstr "doit être une IP privée"
#: tiramisu/option/ipoption.py:141
#: tiramisu/option/ipoption.py:147
msgid "\"{0}\" is not in network \"{1}\" (\"{2}\")"
msgstr "\"{0}\" n'est pas dans le réseau \"{1}\" (\"{2}\")"
#: tiramisu/option/ipoption.py:157
#: tiramisu/option/ipoption.py:163
msgid "ip_network needs an IP, a network and a netmask"
msgstr "ip_network nécessite une IP, un réseau et un masque de réseau"
#: tiramisu/option/ipoption.py:163
#: tiramisu/option/ipoption.py:169
msgid "\"{4}\" is not in network \"{0}\"/\"{1}\" (\"{2}\"/\"{3}\")"
msgstr "\"{4}\" n'est pas dans le réseau \"{0}\"/\"{1}\" (\"{2}\"/\"{3}\")"
@ -657,51 +676,51 @@ msgstr ""
"requirement mal formé pour l'option \"{0}\" ne doit pas être dans une "
"suiveuse pour \"{1}\""
#: tiramisu/option/netmaskoption.py:34
#: tiramisu/option/netmaskoption.py:35
msgid "netmask address"
msgstr "adresse netmask"
#: tiramisu/option/netmaskoption.py:59
#: tiramisu/option/netmaskoption.py:60
msgid "network_netmask needs a network and a netmask"
msgstr "network_netmask nécessite un réseau et un masque de réseau"
#: tiramisu/option/netmaskoption.py:68
#: tiramisu/option/netmaskoption.py:69
msgid "with netmask \"{0}\" (\"{1}\")"
msgstr "avec le masque \"{0}\" (\"{1}\")"
#: tiramisu/option/netmaskoption.py:71
#: tiramisu/option/netmaskoption.py:72
msgid "with network \"{0}\" (\"{1}\")"
msgstr "avec le réseau \"{0}\" (\"{1}\")"
#: tiramisu/option/netmaskoption.py:82
#: tiramisu/option/netmaskoption.py:83
msgid "ip_netmask needs an IP and a netmask"
msgstr "ip_netmask nécessite une IP et un masque de réseau"
#: tiramisu/option/netmaskoption.py:90
#: tiramisu/option/netmaskoption.py:91
msgid "this is a network with netmask \"{0}\" (\"{1}\")"
msgstr "c'est une adresse réseau avec le masque \"{0}\" (\"{1}\")"
#: tiramisu/option/netmaskoption.py:94
#: tiramisu/option/netmaskoption.py:95
msgid "this is a broadcast with netmask \"{0}\" (\"{1}\")"
msgstr "c'est une adresse broadcast avec le masque \"{0}\" (\"{1}\")"
#: tiramisu/option/netmaskoption.py:99
#: tiramisu/option/netmaskoption.py:100
msgid "IP \"{0}\" (\"{1}\") is the network"
msgstr "IP \"{0}\" (\"{1}\") est le réseau"
#: tiramisu/option/netmaskoption.py:103
#: tiramisu/option/netmaskoption.py:104
msgid "IP \"{0}\" (\"{1}\") is the broadcast"
msgstr "IP \"{0}\" (\"{1}\") est l'adresse de broadcast"
#: tiramisu/option/networkoption.py:31
#: tiramisu/option/networkoption.py:32
msgid "network address"
msgstr "adresse réseau"
#: tiramisu/option/networkoption.py:90
#: tiramisu/option/networkoption.py:91
msgid "shouldn't be reserved network"
msgstr "ne devrait pas être une IP réservée"
#: tiramisu/option/networkoption.py:92
#: tiramisu/option/networkoption.py:93
msgid "mustn't be reserved network"
msgstr "ne doit pas être une IP réservée"
@ -734,89 +753,89 @@ msgstr ""
"la valeur de la default_multi \"{0}\" est invalide pour l'option \"{1}\", "
"doit être une liste pour une submulti"
#: tiramisu/option/option.py:255
#: tiramisu/option/option.py:259
msgid "invalid value \"{}\", this value is already in \"{}\""
msgstr "valeur invalide \"{}\", cette valeur est déjà dans \"{}\""
#: tiramisu/option/option.py:285
#: tiramisu/option/option.py:289
msgid "which must not be a list"
msgstr "qui ne doit pas être une liste"
#: tiramisu/option/option.py:319 tiramisu/option/option.py:328
#: tiramisu/option/option.py:323 tiramisu/option/option.py:332
msgid "which must be a list"
msgstr "qui doit être une liste"
#: tiramisu/option/option.py:333
#: tiramisu/option/option.py:337
msgid "which \"{}\" must be a list of list"
msgstr "lequel \"{}\" doit être une liste de liste"
#: tiramisu/option/option.py:375
#: tiramisu/option/option.py:379
msgid "default value not allowed if option \"{0}\" is calculated"
msgstr ""
"la valeur par défaut n'est pas possible si l'option \"{0}\" est calculée"
#: tiramisu/option/option.py:423
#: tiramisu/option/option.py:427
msgid "'{0}' ({1}) cannot add consistency, option is read-only"
msgstr ""
"'{0}' ({1}) ne peut ajouter de consistency, l'option est en lecture seul"
#: tiramisu/option/option.py:431
#: tiramisu/option/option.py:435
msgid "consistency {0} not available for this option"
msgstr "consistency {0} non valable pour cette option"
#: tiramisu/option/option.py:438
#: tiramisu/option/option.py:442
msgid "unknown parameter {0} in consistency"
msgstr "paramètre inconnu {0} dans un test de consistance"
#: tiramisu/option/option.py:550 tiramisu/option/option.py:555
#: tiramisu/option/option.py:554 tiramisu/option/option.py:559
msgid "cannot add consistency with submulti option"
msgstr "ne peut ajouter de test de consistence a une option submulti"
#: tiramisu/option/option.py:556
#: tiramisu/option/option.py:560
msgid "consistency must be set with an option, not {}"
msgstr ""
"test de consistence doit être renseigné avec une option, et non avec {}"
#: tiramisu/option/option.py:559 tiramisu/option/option.py:567
#: tiramisu/option/option.py:563 tiramisu/option/option.py:571
msgid ""
"almost one option in consistency is in a dynoptiondescription but not all"
msgstr ""
"au moins une option dans le test de consistance est dans une "
"dynoptiondescription mais pas toutes"
#: tiramisu/option/option.py:563
#: tiramisu/option/option.py:567
msgid "option in consistency must be in same dynoptiondescription"
msgstr ""
"option dans une consistency doit être dans le même dynoptiondescription"
#: tiramisu/option/option.py:570
#: tiramisu/option/option.py:574
msgid "cannot add consistency with itself"
msgstr "ne peut ajouter une consistency avec lui même"
#: tiramisu/option/option.py:572
#: tiramisu/option/option.py:576
msgid "every options in consistency must be multi or none"
msgstr ""
"toutes les options d'une consistency doivent être multi ou ne pas l'être"
#: tiramisu/option/option.py:612
#: tiramisu/option/option.py:616
msgid "unexpected length of \"{}\" in constency \"{}\", should be \"{}\""
msgstr ""
"longueur inconsistante pour \"{}\" dans le test de consistence \"{}\", "
"devrait être \"{}\""
#: tiramisu/option/option.py:712
#: tiramisu/option/option.py:716
msgid "should be different from the value of \"{}\""
msgstr "devrait être différent de la valeur de \"{}\""
#: tiramisu/option/option.py:714
#: tiramisu/option/option.py:718
msgid "must be different from the value of \"{}\""
msgstr "doit être différent de la valeur de \"{}\""
#: tiramisu/option/option.py:717
#: tiramisu/option/option.py:721
msgid "value for {} should be different"
msgstr "valeur pour {} devrait être différent"
#: tiramisu/option/option.py:719
#: tiramisu/option/option.py:723
msgid "value for {} must be different"
msgstr "valeur pour {} doit être différent"
@ -835,7 +854,7 @@ msgstr ""
msgid "the dynoption \"{0}\" cannot have \"force_store_value\" property"
msgstr "la dynoption \"{0}\" ne peut avoir la propriété \"force_store_value\""
#: tiramisu/option/optiondescription.py:97 tiramisu/setting.py:643
#: tiramisu/option/optiondescription.py:97 tiramisu/setting.py:665
msgid ""
"a leader ({0}) cannot have \"force_default_on_freeze\" or "
"\"force_metaconfig_on_freeze\" property without \"frozen\""
@ -910,35 +929,35 @@ msgstr "ne peut changer group_type si déjà spécifié (ancien {0}, nouveau {1}
msgid "group_type: {0} not allowed"
msgstr "group_type : {0} non autorisé"
#: tiramisu/option/passwordoption.py:31
#: tiramisu/option/passwordoption.py:32
msgid "password"
msgstr "mot de passe"
#: tiramisu/option/portoption.py:43
#: tiramisu/option/portoption.py:44
msgid "port"
msgstr "port"
#: tiramisu/option/portoption.py:80
#: tiramisu/option/portoption.py:81
msgid "inconsistency in allowed range"
msgstr "inconsistence dans la plage autorisée"
#: tiramisu/option/portoption.py:85
#: tiramisu/option/portoption.py:86
msgid "max value is empty"
msgstr "la valeur maximum est vide"
#: tiramisu/option/portoption.py:110
#: tiramisu/option/portoption.py:111
msgid "range must have two values only"
msgstr "un rang doit avoir deux valeurs seulement"
#: tiramisu/option/portoption.py:112
#: tiramisu/option/portoption.py:113
msgid "first port in range must be smaller than the second one"
msgstr "le premier port d'un rang doit être plus petit que le second"
#: tiramisu/option/portoption.py:122
#: tiramisu/option/portoption.py:123
msgid "must be an integer between {0} and {1}"
msgstr "doit être une nombre entre {0} et {1}"
#: tiramisu/option/stroption.py:32
#: tiramisu/option/stroption.py:33
msgid "string"
msgstr "texte"
@ -950,23 +969,23 @@ msgstr "symlinkoption mal formé, doit être une option pour symlink {0}"
msgid "unknown option \"{0}\" in syndynoptiondescription \"{1}\""
msgstr "l'option \"{0}\" inconnue dans la syndynoptiondescription \"{1}\""
#: tiramisu/option/urloption.py:33
#: tiramisu/option/urloption.py:34
msgid "URL"
msgstr "URL"
#: tiramisu/option/urloption.py:43
#: tiramisu/option/urloption.py:44
msgid "must start with http:// or https://"
msgstr "doit débuter par http:// ou https://"
#: tiramisu/option/urloption.py:61
#: tiramisu/option/urloption.py:62
msgid "port must be an between 0 and 65536"
msgstr "port doit être entre 0 et 65536"
#: tiramisu/option/urloption.py:70
#: tiramisu/option/urloption.py:71
msgid "must ends with a valid resource name"
msgstr "doit finir par un nom de ressource valide"
#: tiramisu/option/usernameoption.py:31
#: tiramisu/option/usernameoption.py:32
msgid "username"
msgstr "nom d'utilisateur"
@ -978,7 +997,7 @@ msgstr "ne peut redéfinir ({0})"
msgid "can't unbind {0}"
msgstr "ne peut supprimer ({0})"
#: tiramisu/setting.py:518
#: tiramisu/setting.py:519
msgid ""
"malformed requirements imbrication detected for option: '{0}' with "
"requirement on: '{1}'"
@ -986,48 +1005,56 @@ msgstr ""
"imbrication de requirements mal formés detectée pour l'option : '{0}' avec "
"requirement sur : '{1}'"
#: tiramisu/setting.py:566
#: tiramisu/setting.py:582
msgid ""
"cannot access to option \"{0}\" because required option \"{1}\" has {2} {3}"
msgstr ""
"ne peut accéder à l'option \"{0}\" parce que l'option requise \"{1}\" a {2} "
"{3}"
#: tiramisu/setting.py:590
#: tiramisu/setting.py:608
msgid "the calculated value is {0}"
msgstr "valeurs calculées est {0}"
#: tiramisu/setting.py:610
msgid "the calculated value is not {0}"
msgstr "valeurs calculées n'est pas {0}"
#: tiramisu/setting.py:614
msgid "the value of \"{0}\" is {1}"
msgstr "la valeur de \"{0}\" est {1}"
#: tiramisu/setting.py:592
#: tiramisu/setting.py:616
msgid "the value of \"{0}\" is not {1}"
msgstr "la valeur de \"{0}\" n'est pas {1}"
#: tiramisu/setting.py:633
#: tiramisu/setting.py:655
msgid "cannot set property {} for option \"{}\" this property is calculated"
msgstr ""
"ne peut ajouter la propriété {} pour l'option \"{}\" cette propriété est "
"calculée"
#: tiramisu/setting.py:638
#: tiramisu/setting.py:660
msgid "can't assign property to the symlinkoption \"{}\""
msgstr "ne peut assigner une propriété à une symlinkoption \"{}\""
#: tiramisu/setting.py:670
#: tiramisu/setting.py:692
msgid "permissive must be a frozenset"
msgstr "une permissive doit être de type frozenset"
#: tiramisu/setting.py:674
#: tiramisu/setting.py:696
msgid "can't assign permissive to the symlinkoption \"{}\""
msgstr "ne peut assigner une permissive à la symlinkoption \"{}\""
#: tiramisu/setting.py:681
#: tiramisu/setting.py:703
msgid "cannot add those permissives: {0}"
msgstr "ne peut ajouter ces permissives : {0}"
#: tiramisu/setting.py:698
#: tiramisu/setting.py:720
msgid "can't reset properties to the symlinkoption \"{}\""
msgstr "ne peut réinitialiser les propriétés de la symlinkoption \"{}\""
#: tiramisu/setting.py:713
#: tiramisu/setting.py:735
msgid "can't reset permissives to the symlinkoption \"{}\""
msgstr "ne peut réinitialiser les permissive de la symlinkoption \"{}\""
@ -1061,19 +1088,45 @@ msgstr "ne peut supprimer une session non persistante"
msgid "cannot change setting when connexion is already opened"
msgstr "ne peut changer les paramètres quand une connexion est déjà ouverte"
#: tiramisu/value.py:424
#: tiramisu/todict.py:66
msgid "context is not supported from now for {}"
msgstr "context n'est pas supporté maintenant pour {}"
#: tiramisu/todict.py:82 tiramisu/todict.py:91
msgid "option {} only works when remotable is not \"none\""
msgstr "l'option {} ne fonctionne que si remotable is \"none\""
#: tiramisu/todict.py:706 tiramisu/todict.py:839
msgid "unknown form {}"
msgstr "form {} inconnu"
#: tiramisu/todict.py:751
msgid "not in current area"
msgstr "n'est pas dans l'espace courant"
#: tiramisu/todict.py:771
msgid "only multi option can have action \"add\", but \"{}\" is not a multi"
msgstr ""
"seules des options multiples peuvent avoir l'action \"add\", mais \"{}\" "
"n'est pas une valeur multiple"
#: tiramisu/todict.py:773
msgid "unknown action"
msgstr "action inconnue"
#: tiramisu/value.py:427
msgid "can't set owner for the symlinkoption \"{}\""
msgstr "ne peut spécifier d'utilisateur à la symlinkoption \"{}\""
#: tiramisu/value.py:427 tiramisu/value.py:639
#: tiramisu/value.py:430 tiramisu/value.py:642
msgid "set owner \"{0}\" is forbidden"
msgstr "assigner l'utilisateur \"{0}\" est interdit"
#: tiramisu/value.py:430
#: tiramisu/value.py:433
msgid "no value for {0} cannot change owner to {1}"
msgstr "pas de valeur pour {0} ne peut changer d'utilisateur pour {1}"
#: tiramisu/value.py:508
#: tiramisu/value.py:511
msgid "index \"{}\" is higher than the length \"{}\" for option \"{}\""
msgstr ""
"l'index \"{}\" est supérieur à la longueur de l'option \"{}\" pour l'option "

View File

@ -5,7 +5,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2019-04-07 10:39+CEST\n"
"POT-Creation-Date: 2019-04-08 08:41+CEST\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,71 +15,75 @@ msgstr ""
"Generated-By: pygettext.py 1.5\n"
#: tiramisu/api.py:58
#: tiramisu/api.py:59
msgid "Settings:"
msgstr ""
#: tiramisu/api.py:59
#: tiramisu/api.py:60
msgid "Access to option without verifying permissive properties"
msgstr ""
#: tiramisu/api.py:60
#: tiramisu/api.py:61
msgid "Access to option without property restriction"
msgstr ""
#: tiramisu/api.py:64
#: tiramisu/api.py:65
msgid "Call: {}"
msgstr ""
#: tiramisu/api.py:66
#: tiramisu/api.py:67
msgid "Commands:"
msgstr ""
#: tiramisu/api.py:103
#: tiramisu/api.py:104
msgid "index \"{}\" is higher than the leadership length \"{}\" for option \"{}\""
msgstr ""
#: tiramisu/api.py:108
#: tiramisu/api.py:109
msgid "option must not be an optiondescription"
msgstr ""
#: tiramisu/api.py:135
#: tiramisu/api.py:136
msgid "index must be set with the follower option \"{}\""
msgstr ""
#: tiramisu/api.py:138
#: tiramisu/api.py:139
msgid "unknown method {}"
msgstr ""
#: tiramisu/api.py:360
#: tiramisu/api.py:361
msgid "cannot add this property: \"{0}\""
msgstr ""
#: tiramisu/api.py:504 tiramisu/config.py:252
#: tiramisu/api.py:505 tiramisu/config.py:252
msgid "can't delete a SymLinkOption"
msgstr ""
#: tiramisu/api.py:637 tiramisu/api.py:1326
#: tiramisu/api.py:639 tiramisu/api.py:1375
msgid "please specify a valid sub function ({})"
msgstr ""
#: tiramisu/api.py:700 tiramisu/api.py:1146
#: tiramisu/api.py:702 tiramisu/api.py:1176
msgid "unknown list type {}"
msgstr ""
#: tiramisu/api.py:702 tiramisu/api.py:1148
#: tiramisu/api.py:704 tiramisu/api.py:1178
msgid "unknown group_type: {0}"
msgstr ""
#: tiramisu/api.py:975
#: tiramisu/api.py:753 tiramisu/api.py:1208
msgid "please use .dict() before .updates()"
msgstr ""
#: tiramisu/api.py:1000
msgid "properties must be a set"
msgstr ""
#: tiramisu/api.py:981 tiramisu/api.py:1003
#: tiramisu/api.py:1006 tiramisu/api.py:1028
msgid "unknown when {} (must be in append or remove)"
msgstr ""
#: tiramisu/api.py:993 tiramisu/api.py:1015 tiramisu/config.py:1249
#: tiramisu/api.py:1018 tiramisu/api.py:1040 tiramisu/config.py:1249
msgid "unknown type {}"
msgstr ""
@ -959,6 +963,30 @@ msgstr ""
msgid "cannot change setting when connexion is already opened"
msgstr ""
#: tiramisu/todict.py:66
msgid "context is not supported from now for {}"
msgstr ""
#: tiramisu/todict.py:82 tiramisu/todict.py:91
msgid "option {} only works when remotable is not \"none\""
msgstr ""
#: tiramisu/todict.py:706 tiramisu/todict.py:839
msgid "unknown form {}"
msgstr ""
#: tiramisu/todict.py:751
msgid "not in current area"
msgstr ""
#: tiramisu/todict.py:771
msgid "only multi option can have action \"add\", but \"{}\" is not a multi"
msgstr ""
#: tiramisu/todict.py:773
msgid "unknown action"
msgstr ""
#: tiramisu/value.py:427
msgid "can't set owner for the symlinkoption \"{}\""
msgstr ""

875
tiramisu/todict.py Normal file
View File

@ -0,0 +1,875 @@
# -*- coding: utf-8 -*-
import warnings
import sys
from copy import copy
from collections import OrderedDict
from .error import ValueWarning, ValueErrorWarning, PropertiesOptionError
from . import SynDynOption, RegexpOption, ChoiceOption, ParamContext, ParamOption
from .i18n import _
TYPES = {'SymLinkOption': 'symlink',
'IntOption': 'integer',
'FloatOption': 'integer',
'ChoiceOption': 'choice',
'BoolOption': 'boolean',
'PasswordOption': 'password',
'PortOption': 'integer',
'DateOption': 'date',
'DomainnameOption': 'domainname',
'StrOption': 'string'
}
INPUTS = ['string',
'integer',
'filename',
'password',
'email',
'username',
'ip',
'domainname']
ACTION_HIDE = ['hidden', 'disabled']
# return always warning (even if same warning is already returned)
warnings.simplefilter("always", ValueWarning)
class Callbacks(object):
def __init__(self, tiramisu_web):
self.tiramisu_web = tiramisu_web
self.clearable = tiramisu_web.clearable
self.remotable = tiramisu_web.remotable
self.callbacks = []
def add(self,
path,
childapi,
options,
schema,
force_store_value):
self.callbacks.append((path, childapi, options, schema, force_store_value))
def manage_callbacks(self,
path,
childapi,
options,
callback,
callback_params,
form,
schema):
if callback_params is not None:
remote = True
for callback_param in callback_params.args:
if isinstance(callback_param, ParamContext):
raise ValueError(_('context is not supported from now for {}').format(path))
if isinstance(callback_param, ParamOption):
if callback.__name__ == 'tiramisu_copy':
if self.clearable == 'minimum':
form.setdefault(path, {})['clearable'] = True
if form.get(path, {}).get('remote') is not True and form[options[callback_param.option]].get('remote', False) is not True:
form.setdefault(options[callback_param.option], {})
form[options[callback_param.option]].setdefault('copy', []).append(path)
remote = False
elif options.get(callback_param.option) is not None:
#form.setdefault(options[callback_param.option], {})
form.setdefault(path, {})
form[path]['remote'] = True
#break
if remote:
if self.remotable == 'none':
raise ValueError(_('option {} only works when remotable is not "none"').format(path))
form.setdefault(path, {})['remote'] = True
else:
if 'expire' not in childapi.property.get() and childapi.owner.isdefault():
schema[path]['value'] = childapi.value.get()
if self.clearable == 'minimum':
form.setdefault(path, {})['clearable'] = True
else:
if self.remotable == 'none':
raise ValueError(_('option {} only works when remotable is not "none"').format(path))
# FIXME is not default, show calculate
form.setdefault(path, {})['remote'] = True
def process(self,
form):
for path, childapi, options, schema, force_store_value in self.callbacks:
if not childapi.option.isoptiondescription():
callback, callback_params = childapi.option.callbacks()
if callback is not None:
if force_store_value and self.clearable != 'all':
return
self.manage_callbacks(path,
childapi,
options,
callback,
callback_params,
form,
schema)
class Consistencies(object):
def __init__(self, tiramisu_web):
self.not_equal = []
self.options = {}
self.tiramisu_web = tiramisu_web
def add(self, path, childapi):
child = childapi.option.get()
if isinstance(child, SynDynOption):
child = child._impl_getopt()
self.options[child] = path
if not childapi.option.isoptiondescription():
for consistency in childapi.option.consistencies():
cons_id, func, all_cons_opts, params = consistency
if func == '_cons_not_equal':
options = []
for option in all_cons_opts:
option = option()
options.append(option)
# FIXME transitive
self.not_equal.append((options, params.get('warnings_only')))
def process(self, form):
for not_equal, warnings_only in self.not_equal:
not_equal_option = []
for option in not_equal:
not_equal_option.append(self.options[option])
for idx, path in enumerate(not_equal_option):
if form.get(path, {}).get('remote') is True:
continue
options = copy(not_equal_option)
options.pop(idx)
form.setdefault(path, {}).setdefault('not_equal',
{'options': []})
form[path]['not_equal']['options'].extend(options)
if warnings_only or getattr(option, '_warnings_only', False):
form[path]['not_equal']['warnings'] = True
class Requires(object):
def __init__(self, tiramisu_web):
self.requires = {}
self.options = {}
self.tiramisu_web = tiramisu_web
self.config = tiramisu_web.config
self.remotable = tiramisu_web.remotable
def manage_requires(self,
require_obj,
childapi,
path,
form,
action_hide,
current_action):
for requires in childapi.option.requires():
for require in requires:
options, action, inverse, \
transitive, same_action, operator = require
if transitive is False:
# transitive to "False" not supported yet for a requirement
if require_obj.remotable == 'none':
raise ValueError('require set for {} but remotable is "none"'
''.format(path))
form.setdefault(path, {'key': path})['remote'] = True
return
if same_action is False:
# same_action to "False" not supported yet for a requirement
if require_obj.remotable == 'none':
raise ValueError('require set for {} but remotable is "none"'
''.format(path))
form.setdefault(path, {'key': path})['remote'] = True
return
if operator == 'and':
# operator "and" not supported yet for a requirement
if require_obj.remotable == 'none':
raise ValueError('require set for {} but remotable is "none"'
''.format(path))
form.setdefault(path, {'key': path})['remote'] = True
return
for option, expected in options:
option_path = require_obj.options.get(option)
if option_path is not None and action in action_hide:
if current_action is None:
current_action = action
elif current_action != action:
if require_obj.remotable == 'none':
raise ValueError('require set for {} but remotable is "none"'
''.format(path))
form.setdefault(option_path, {'key': option_path})['remote'] = True
for exp in expected:
if inverse:
act = 'show'
inv_act = 'hide'
else:
act = 'hide'
inv_act = 'show'
require_obj.requires.setdefault(path,
{'expected': {}}
)['expected'].setdefault(exp,
{}).setdefault(act,
[]).append(option_path)
if isinstance(option, ChoiceOption):
for value in require_obj.config.unrestraint.option(option_path).value.list():
if value not in expected:
require_obj.requires.setdefault(path,
{'expected': {}}
)['expected'].setdefault(value,
{}).setdefault(inv_act,
[]).append(option_path)
require_obj.requires[path].setdefault('default', {}).setdefault(inv_act, []).append(option_path)
else:
if require_obj.remotable == 'none':
raise ValueError('require set for {} but remotable est "none"'
''.format(path))
form.setdefault(option_path, {'key': option_path})['remote'] = True
def add(self, path, childapi, form):
#collect id of all options
child = childapi.option.get()
if isinstance(child, SynDynOption):
child = child._impl_getopt()
self.options[child] = path
current_action = None
self.manage_requires(self,
childapi,
path,
form,
ACTION_HIDE,
current_action)
def is_remote(self, path, form):
if self.remotable == 'all':
return True
else:
return form.get(path) and form[path].get('remote', False)
def process(self, form):
dependencies = {}
for path, values in self.requires.items():
if form.get(path, {}).get('remote') is True:
continue
if 'default' in values:
for option in values['default'].get('show', []):
if path == option:
form.setdefault(path, {'key': path})['remote'] = True
if not self.is_remote(option, form):
dependencies.setdefault(option,
{'default': {}, 'expected': {}}
)['default'].setdefault('show', [])
if path not in dependencies[option]['default']['show']:
dependencies[option]['default']['show'].append(path)
for option in values['default'].get('hide', []):
if path == option:
form.setdefault(path, {'key': path})['remote'] = True
if not self.is_remote(option, form):
dependencies.setdefault(option,
{'default': {}, 'expected': {}}
)['default'].setdefault('hide', [])
if path not in dependencies[option]['default']['hide']:
dependencies[option]['default']['hide'].append(path)
for expected, actions in values['expected'].items():
if expected is None:
expected = ''
for option in actions.get('show', []):
if path == option:
form.setdefault(path, {'key': path})['remote'] = True
if not self.is_remote(option, form):
dependencies.setdefault(option,
{'expected': {}}
)['expected'].setdefault(expected,
{}).setdefault('show', [])
if path not in dependencies[option]['expected'][expected]['show']:
dependencies[option]['expected'][expected]['show'].append(path)
for option in actions.get('hide', []):
if path == option:
form.setdefault(path, {'key': path})['remote'] = True
if not self.is_remote(option, form):
dependencies.setdefault(option,
{'expected': {}}
)['expected'].setdefault(expected,
{}).setdefault('hide', [])
if path not in dependencies[option]['expected'][expected]['hide']:
dependencies[option]['expected'][expected]['hide'].append(path)
for path, dependency in dependencies.items():
form[path]['dependencies'] = dependency
class TiramisuDict:
# propriete:
# hidden
# mandatory
# editable
# FIXME model:
# #optionnel mais qui bouge
# choices/suggests
# warning
#
# #bouge
# owner
# properties
def __init__(self,
config,
root=None,
clearable="all",
remotable="minimum"):
if 'demoting_error_warning' not in config.property.get():
raise ValueError('demoting_error_warning property is mandatory')
self.config = config
self.root = root
self.requires = None
self.callbacks = None
self.consistencies = None
#all, minimum, none
self.clearable = clearable
#all, minimum, none
self.remotable = remotable
self.context_properties = self.config.property.get()
self.context_permissives = self.config.permissive.get()
def add_help(self,
obj,
childapi):
hlp = childapi.information.get('help', None)
if hlp is not None:
obj['help'] = hlp
def get_list(self, root, subchildapi):
for childapi in subchildapi.list('all'):
childname = childapi.option.name()
if root is None:
path = childname
else:
path = root + '.' + childname
yield path, childapi
def walk(self,
root,
subchildapi,
schema,
model,
form,
order,
updates_status,
init=False):
if init:
if form is not None:
self.requires = Requires(self)
self.consistencies = Consistencies(self)
self.callbacks = Callbacks(self)
else:
init = False
if subchildapi is None:
if root is None:
subchildapi = self.config.unrestraint.option
else:
subchildapi = self.config.unrestraint.option(root)
isleadership = False
else:
isleadership = subchildapi.option.isleadership()
leader_len = None
for path, childapi in self.get_list(root, subchildapi):
if isleadership and leader_len is None:
leader_len = childapi.value.len()
props_no_requires = set(childapi.option.properties())
if form is not None:
self.requires.add(path,
childapi,
form)
self.consistencies.add(path,
childapi)
self.callbacks.add(path,
childapi,
self.requires.options,
schema,
'force_store_value' in props_no_requires)
childapi_option = childapi.option
if model is not None:
self.gen_model(model,
childapi,
path,
leader_len,
props_no_requires,
updates_status)
if order is not None:
order.append(path)
if childapi.option.isoptiondescription():
if childapi_option.isleadership():
type_ = 'array'
else:
type_ = 'object'
if schema is not None:
schema[path] = {'name': path,
'properties': OrderedDict(),
'type': type_}
subschema = schema[path]['properties']
else:
subschema = schema
self.walk(path,
childapi,
subschema,
model,
form,
order,
updates_status)
else:
child = childapi_option.get()
childtype = child.__class__.__name__
if childtype == 'SynDynOption':
childtype = child._impl_getopt().__class__.__name__
if childapi_option.issymlinkoption():
web_type = 'symlink'
else:
web_type = childapi_option.type()
value = childapi.option.default()
if value not in [[], None]:
has_value = True
else:
value = None
has_value = False
is_multi = childapi_option.ismulti()
if is_multi:
default = childapi_option.defaultmulti()
if default not in [None, []]:
has_value = True
else:
default = None
else:
default = None
if schema is not None:
self.gen_schema(schema,
childapi,
childapi_option,
path,
props_no_requires,
value,
default,
is_multi,
web_type)
if form is not None:
self.gen_form(form,
web_type,
path,
child,
childapi_option,
childtype,
has_value)
if schema is not None:
schema[path]['title'] = childapi_option.doc()
self.add_help(schema[path],
childapi)
if init and form is not None:
self.callbacks.process(form)
self.requires.process(form)
self.consistencies.process(form)
del self.requires
del self.consistencies
def gen_schema(self,
schema,
childapi,
childapi_option,
path,
props_no_requires,
value,
default,
is_multi,
web_type):
schema[path] = {'name': path,
'type': web_type}
if childapi_option.issymlinkoption():
schema[path]['opt_path'] = childapi_option.get().impl_getopt().impl_getpath()
else:
if value is not None:
schema[path]['value'] = value
if default is not None:
schema[path]['default'] = default
if is_multi:
schema[path]['isMulti'] = is_multi
if childapi_option.issubmulti():
schema[path]['isSubMulti'] = True
if 'auto_freeze' in props_no_requires:
schema[path]['autoFreeze'] = True
if web_type == 'choice':
schema[path]['enum'] = childapi.value.list()
empty_is_required = not childapi.option.isfollower() and is_multi
if (empty_is_required and not 'empty' in props_no_requires) or \
(not empty_is_required and not 'mandatory' in props_no_requires):
schema[path]['enum'] = [''] + list(schema[path]['enum'])
def gen_form(self,
form,
web_type,
path,
child,
childapi_option,
childtype,
has_value):
obj_form = {}
if path in form:
obj_form.update(form[path])
if not childapi_option.issymlinkoption():
if self.clearable == 'all':
obj_form['clearable'] = True
if has_value and self.clearable != 'none':
obj_form['clearable'] = True
if self.remotable == 'all' or childapi_option.has_dependency():
obj_form['remote'] = True
if web_type == 'integer':
obj_form['allowedpattern'] = '[0-9]'
if isinstance(child, RegexpOption):
obj_form['pattern'] = child._regexp.pattern
if web_type == 'domainname':
obj_form['pattern'] = child.impl_get_extra('_domain_re').pattern
if childtype in ['IPOption', 'NetworkOption', 'NetmaskOption']:
#FIXME only from 0.0.0.0 to 255.255.255.255
obj_form['pattern'] = r'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
if childtype == 'FloatOption':
obj_form['step'] = 'any'
if childtype == 'PortOption':
obj_form['min'] = child.impl_get_extra('_min_value')
obj_form['max'] = child.impl_get_extra('_max_value')
if web_type == 'choice':
obj_form['type'] = 'choice'
elif web_type in INPUTS:
obj_form['type'] = 'input'
if obj_form:
form[path] = obj_form
def calc_raises_properties(self, childapi):
old_properties = childapi._option_bag.config_bag.properties
del childapi._option_bag.config_bag.properties
ret = childapi.option.properties(only_raises=True)
childapi._option_bag.config_bag.properties = old_properties
return ret
def _gen_model_properties(self,
childapi,
path,
index,
props_no_requires):
obj = {}
isfollower = childapi.option.isfollower()
if index is None and isfollower:
# cannot calculated requires with follower without index
props = props_no_requires
else:
props = set(childapi.property.get())
if self.calc_raises_properties(childapi):
obj['display'] = False
if not isfollower and childapi.option.ismulti():
if 'empty' in props:
obj['required'] = True
props.remove('empty')
if 'mandatory' in props:
obj['needs_len'] = True
props.remove('mandatory')
elif 'mandatory' in props:
obj['required'] = True
props.remove('mandatory')
if 'frozen' in props:
obj['readOnly'] = True
props.remove('frozen')
if 'hidden' in props:
obj['hidden'] = True
props.remove('hidden')
if 'disabled' in props:
obj['hidden'] = True
props.remove('disabled')
if props:
lprops = list(props)
lprops.sort()
obj['properties'] = lprops
return obj
def gen_model(self,
model,
childapi,
path,
leader_len,
props_no_requires,
updates_status):
if childapi.option.isoptiondescription():
props = set(childapi.property.get())
obj = {}
if self.calc_raises_properties(childapi):
obj['display'] = False
if props:
lprops = list(props)
lprops.sort()
obj['properties'] = lprops
if 'hidden' in props or 'disabled' in props:
obj['hidden'] = True
try:
self.config.option(path).option.get()
except PropertiesOptionError:
pass
else:
obj = self._gen_model_properties(childapi,
path,
None,
props_no_requires)
if childapi.option.isfollower():
for index in range(leader_len):
follower_childapi = self.config.unrestraint.option(path, index)
sobj = self._gen_model_properties(follower_childapi,
path,
index,
props_no_requires)
self._get_model_value(follower_childapi,
path,
sobj,
index,
updates_status)
if sobj:
model.setdefault(path, {})[str(index)] = sobj
else:
self._get_model_value(childapi,
path,
obj,
None,
updates_status)
if obj:
if not childapi.option.isoptiondescription() and childapi.option.isfollower():
model.setdefault(path, {})[None] = obj
else:
model[path] = obj
def _get_model_value(self,
childapi,
path,
obj,
index,
updates_status):
# FIXME unrestraint ...
try:
nchildapi = self.config.option(path, index=index)
with warnings.catch_warnings(record=True) as warns:
value = nchildapi.value.get()
self._get_value_with_exception(obj,
childapi,
warns)
except PropertiesOptionError:
value = childapi.value.get()
warns = []
if value is not None and value != []:
obj['value'] = value
obj['owner'] = childapi.owner.get()
def _get_value_with_exception(self,
obj,
childapi,
values):
for value in values:
if isinstance(value.message, ValueErrorWarning):
value.message.prefix = ''
if childapi.option.isleader():
obj.setdefault('invalid', [])
obj['invalid'].append({'error': str(value.message),
'index': value.message.index})
else:
obj.setdefault('error', [])
obj['error'].append(str(value.message))
obj['invalid'] = True
else:
obj.setdefault('warnings', [])
obj['warnings'].append(str(value.message))
obj['hasWarnings'] = True
def get_form(self, form):
ret = []
buttons = []
dict_form = OrderedDict()
for form_ in form:
if 'key' in form_:
dict_form[form_['key']] = form_
elif form_.get('type') == 'submit':
if 'cmd' not in form_:
form_['cmd'] = 'submit'
buttons.append(form_)
else:
raise ValueError(_('unknown form {}').format(form_))
for key, form_ in self.form.items():
form_['key'] = key
if key in dict_form:
form_.update(dict_form[key])
ret.append(form_)
ret.extend(buttons)
return ret
def del_value(self, childapi, path, index):
if index is not None and childapi.option.isleader():
childapi.value.pop(index)
elif index is None or childapi.option.isfollower():
childapi.value.reset()
else:
multi = childapi.value.get()
multi.pop(index)
childapi.value.set(multi)
def add_value(self, childapi, path, value):
multi = childapi.value.get()
multi.append(value)
childapi.value.set(multi)
def mod_value(self, childapi, path, index, value):
if index is None or childapi.option.isfollower():
childapi.value.set(value)
else:
multi = childapi.value.get()
if not multi and index == 0:
multi.append(value)
else:
multi[index] = value
childapi.value.set(multi)
def apply_updates(self,
oripath,
updates,
model_ori):
updates_status = {}
for update in updates:
path = update['name']
index = update.get('index')
if oripath is not None and not path.startswith(oripath):
raise ValueError(_('not in current area'))
childapi = self.config.option(path)
childapi_option = childapi.option
if childapi_option.isfollower():
childapi = self.config.option(path, index)
with warnings.catch_warnings(record=True) as warns:
#try:
if update['action'] == 'modify':
self.mod_value(childapi,
path,
index,
update.get('value'))
elif update['action'] == 'delete':
self.del_value(childapi,
path,
index)
elif update['action'] == 'add':
if childapi_option.ismulti():
self.add_value(childapi, path, update['value'])
else:
raise ValueError(_('only multi option can have action "add", but "{}" is not a multi').format(path))
else:
raise ValueError(_('unknown action'))
#except ValueError as err:
# updates_status.setdefault(path, {})[index] = err
# continue
if warns != []:
updates_status.setdefault(path, {}).setdefault(index, []).extend(warns)
return updates_status
def set_updates(self,
body):
root_path = self.root
updates = body.get('updates', [])
updates_status = self.apply_updates(root_path,
updates,
body.get('model'))
if 'model' in body:
order = []
old_model = body['model']
new_model = self.todict(order=order,
build_schema=False,
build_form=False,
updates_status=updates_status)['model']
values = {'updates': list_keys(old_model, new_model, order, updates_status),
'model': new_model}
else:
values = None
return values
def todict(self,
custom_form=[],
build_schema=True,
build_model=True,
build_form=True,
order=None,
updates_status={}):
rootpath = self.root
if build_schema:
schema = OrderedDict()
else:
schema = None
if build_model:
model = {}
else:
model = None
if build_form:
form = {}
buttons = []
else:
form = None
self.walk(rootpath,
None,
schema,
model,
form,
order,
updates_status,
init=True)
if build_form:
for form_ in custom_form:
if 'key' in form_:
key = form_.pop('key')
form.setdefault(key, {}).update(form_)
elif form_.get('type') == 'submit':
# FIXME if an Option has a key "null"?
form.setdefault(None, []).append(form_)
else:
raise ValueError(_('unknown form {}').format(form_))
ret = {}
if build_schema:
ret['schema'] = schema
if build_model:
ret['model'] = model
if build_form:
ret['form'] = form
ret['version'] = '1.0'
return ret
def list_keys(model_a, model_b, ordered_key, updates_status):
model_a_dict = {}
model_b_dict = {}
keys_a = set(model_a.keys())
keys_b = set(model_b.keys())
keys = (keys_a ^ keys_b) | set(updates_status.keys())
for key in keys_a & keys_b:
keys_mod_a = set(model_a[key].keys())
keys_mod_b = set(model_b[key].keys())
if keys_mod_a != keys_mod_b:
keys.add(key)
else:
for skey in keys_mod_a:
if model_a[key][skey] != model_b[key][skey]:
keys.add(key)
break
def sort_key(key):
try:
return ordered_key.index(key)
except ValueError:
return -1
return sorted(list(keys), key=sort_key)