bad characters in DomainnameOption could be in warning level
authorEmmanuel Garette <egarette@cadoles.com>
Sun, 26 Oct 2014 08:38:17 +0000 (09:38 +0100)
committerEmmanuel Garette <egarette@cadoles.com>
Sun, 26 Oct 2014 08:38:17 +0000 (09:38 +0100)
ChangeLog
test/test_config_domain.py
test/test_option_default.py
tiramisu/option/option.py
tiramisu/setting.py
tiramisu/value.py
translations/fr/tiramisu.po
translations/tiramisu.pot

index a8d7602..bb4c863 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,7 @@
 Sun Oct 26 08:50:38 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
        * if option is frozen with force_default_on_freeze property, owner
        must be 'default' check property when tried to change owner
+       * bad characters in DomainnameOption could be in warning level
 
 Sat Oct 25 22:48:08 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
        * cannot add unvalaible consistency for an option
index 3edb2cb..85f48b9 100644 (file)
@@ -1,8 +1,10 @@
 import autopath
+import warnings
 from py.test import raises
 
 from tiramisu.config import Config
 from tiramisu.option import DomainnameOption, EmailOption, URLOption, OptionDescription
+from tiramisu.error import ValueWarning
 
 
 def test_domainname():
@@ -15,28 +17,63 @@ def test_domainname():
     c.d = 'toto.com'
     raises(ValueError, "c.d = 'toto'")
     c.d = 'toto3.com'
-    raises(ValueError, "c.d = 'toto3.3la'")
-    #raises(ValueError, "c.d = '3toto.com'")
-    raises(ValueError, "c.d = 'toto.co3'")
     raises(ValueError, "c.d = 'toto_super.com'")
     c.d = 'toto-.com'
     raises(ValueError, "c.d = 'toto..com'")
     #
     c.f = 'toto.com'
     c.f = 'toto'
-    raises(ValueError, "c.f = 'domainnametoolongthathavemorethanmaximumsizeforatruedomainnameanditsnoteasytogeneratesolongdomainnamewithoutrepeatdomainnametoolongthathavemorethanmaximumsizeforatruedomainnameanditsnoteasytogeneratesolongdomainnamewithoutrepeatbutimnotabletodoitnowiendityeah'")
+    c.f = 'domainnametoolongthathavemorethanmaximumsizeforatruedomainnamea'
+    raises(ValueError, "c.f = 'domainnametoolongthathavemorethanmaximumsizeforatruedomainnamean'")
+    c.f = 'domainnametoolongthathavemorethanmaximumsizeforatruedomainnamea.nd'
+    c.f = 'domainnametoolongthathavemorethanmaximumsizeforatruedomainnamea.nditsnoteasytogeneratesolongdomainnamewithoutrepeatdomainnameto.olongthathavemorethanmaximumsizeforatruedomainnameanditsnoteas.ytogeneratesolongdomainnamewithoutrepeatbutimnotabletodoitnowie'
+    raises(ValueError, "c.f = 'domainnametoolongthathavemorethanmaximumsizeforatruedomainnamea.nditsnoteasytogeneratesolongdomainnamewithoutrepeatdomainnameto.olongthathavemorethanmaximumsizeforatruedomainnameanditsnoteas.ytogeneratesolongdomainnamewithoutrepeatbutimnotabletodoitnowien'")
     raises(ValueError, "c.f = 'd'")
+    raises(ValueError, "c.f = 'd.t'")
     #
     c.g = 'toto.com'
     c.g = '192.168.1.0'
     c.g = '192.168.1.29'
 
+
+def test_domainname_warning():
+    d = DomainnameOption('d', '', warnings_only=True)
+    f = DomainnameOption('f', '', allow_without_dot=True, warnings_only=True)
+    g = DomainnameOption('g', '', allow_ip=True, warnings_only=True)
+    od = OptionDescription('a', '', [d, f, g])
+    warnings.simplefilter("always", ValueWarning)
+    c = Config(od)
+    c.read_write()
+    c.d = 'toto.com'
+    raises(ValueError, "c.d = 'toto'")
+    c.d = 'toto3.com'
+    with warnings.catch_warnings(record=True) as w:
+        c.d = 'toto_super.com'
+    assert len(w) == 1
+    c.d = 'toto-.com'
+    raises(ValueError, "c.d = 'toto..com'")
+    #
+    c.f = 'toto.com'
+    c.f = 'toto'
+    c.f = 'domainnametoolongthathavemorethanmaximumsizeforatruedomainnamea'
+    raises(ValueError, "c.f = 'domainnametoolongthathavemorethanmaximumsizeforatruedomainnamean'")
+    c.f = 'domainnametoolongthathavemorethanmaximumsizeforatruedomainnamea.nd'
+    c.f = 'domainnametoolongthathavemorethanmaximumsizeforatruedomainnamea.nditsnoteasytogeneratesolongdomainnamewithoutrepeatdomainnameto.olongthathavemorethanmaximumsizeforatruedomainnameanditsnoteas.ytogeneratesolongdomainnamewithoutrepeatbutimnotabletodoitnowie'
+    raises(ValueError, "c.f = 'domainnametoolongthathavemorethanmaximumsizeforatruedomainnamea.nditsnoteasytogeneratesolongdomainnamewithoutrepeatdomainnameto.olongthathavemorethanmaximumsizeforatruedomainnameanditsnoteas.ytogeneratesolongdomainnamewithoutrepeatbutimnotabletodoitnowien'")
+    raises(ValueError, "c.f = 'd'")
+    raises(ValueError, "c.f = 'd.t'")
+    #
+    c.g = 'toto.com'
+    c.g = '192.168.1.0'
+    c.g = '192.168.1.29'
+
+
 def test_special_domain_name():
     """domain name option that starts with a number or not
     """
     d = DomainnameOption('d', '')
     e = DomainnameOption('e', '', type_='netbios')
-    od = OptionDescription('a', '', [d,e])
+    od = OptionDescription('a', '', [d, e])
     c = Config(od)
     c.read_write()
     c.d = '1toto.com'
index 3eda6e4..f7b483f 100644 (file)
@@ -77,6 +77,11 @@ def test_force_default_on_freeze():
     assert config.getowner(dummy2) == owners.user
     raises(ConfigError, "config.cfgimpl_get_values().setowner(dummy1, owners.frozen)")
     raises(PropertiesOptionError, "config.cfgimpl_get_values().setowner(dummy2, owners.frozen)")
+    raises(PropertiesOptionError, "del(config.dummy1)")
+    setting[dummy1].remove('frozen')
+    del(config.dummy1)
+    setting[dummy1].append('frozen')
+    raises(ConfigError, "config.cfgimpl_get_values().setowner(dummy1, owners.frozen)")
 
 
 def test_force_default_on_freeze_multi():
@@ -99,6 +104,11 @@ def test_force_default_on_freeze_multi():
     assert config.getowner(dummy2) == owners.user
     raises(ConfigError, "config.cfgimpl_get_values().setowner(dummy1, owners.frozenmulti)")
     raises(PropertiesOptionError, "config.cfgimpl_get_values().setowner(dummy2, owners.frozenmulti)")
+    raises(PropertiesOptionError, "del(config.dummy1)")
+    setting[dummy1].remove('frozen')
+    del(config.dummy1)
+    setting[dummy1].append('frozen')
+    raises(ConfigError, "config.cfgimpl_get_values().setowner(dummy1, owners.frozenmulti)")
 
 
 def test_overrides_changes_option_value():
index d311173..412553c 100644 (file)
@@ -408,23 +408,8 @@ class DomainnameOption(Option):
             raise ValueError(_('allow_without_dot must be a boolean'))  # pragma: optional cover
         extra['_allow_ip'] = allow_ip
         extra['_allow_without_dot'] = allow_without_dot
-        end = ''
-        extrachar = ''
-        extrachar_mandatory = ''
-        if extra['_dom_type'] == 'netbios':
-            length = 14  # pragma: optional cover
-        elif extra['_dom_type'] == 'hostname':
-            length = 62  # pragma: optional cover
-        elif extra['_dom_type'] == 'domainname':
-            length = 62
-            if allow_without_dot is False:
-                extrachar_mandatory = '\.'
-            else:
-                extrachar = '\.'  # pragma: optional cover
-            end = '+[a-z]*'
-        extra['_domain_re'] = re.compile(r'^(?:[a-z\d][a-z\d\-{0}]{{,{1}}}{2}){3}$'
-                                         ''.format(extrachar, length,
-                                                   extrachar_mandatory, end))
+        extra['_domain_re'] = re.compile(r'^[a-z\d][a-z\d\-]*$')
+
         super(DomainnameOption, self).__init__(name, doc, default=default,
                                                default_multi=default_multi,
                                                callback=callback,
@@ -438,22 +423,52 @@ class DomainnameOption(Option):
                                                extra=extra)
 
     def _validate(self, value, context=undefined):
+        def _valid_length(val):
+            if len(val) < 2:
+                raise ValueError(_("invalid domainname's length (min 2)"))
+            if len(val) > part_name_length:
+                raise ValueError(_("invalid domainname's length (max {0})"
+                                   "").format(part_name_length))
+
         if self._get_extra('_allow_ip') is True:  # pragma: optional cover
             try:
                 IP('{0}/32'.format(value))
                 return
             except ValueError:
                 pass
-        if self._get_extra('_dom_type') == 'domainname' and \
-                not self._get_extra('_allow_without_dot') and \
-                '.' not in value:  # pragma: optional cover
-            raise ValueError(_("invalid domainname, must have dot"))
-        if len(value) > 255:
-            raise ValueError(_("invalid domainname's length (max 255)"))  # pragma: optional cover
-        if len(value) < 2:
-            raise ValueError(_("invalid domainname's length (min 2)"))  # pragma: optional cover
-        if not self._get_extra('_domain_re').search(value):
-            raise ValueError(_('invalid domainname'))  # pragma: optional cover
+        if self._get_extra('_dom_type') == 'netbios':
+            part_name_length = 15
+        else:
+            part_name_length = 63
+        if self._get_extra('_dom_type') == 'domainname':
+            if not self._get_extra('_allow_without_dot') and not "." in value:
+                raise ValueError(_("invalid domainname, must have dot"))
+            if len(value) > 255:
+                raise ValueError(_("invalid domainname's length (max 255)"))
+            for dom in value.split('.'):
+                _valid_length(dom)
+        else:
+            _valid_length(value)
+
+    def _second_level_validation(self, value, warnings_only):
+        def _valid_char(val):
+            if not self._get_extra('_domain_re').search(val):
+                if warnings_only:
+                    raise ValueError(_('same characters may cause problems'))
+                else:
+                    raise ValueError(_('invalid domainname'))
+        #not for IP
+        if self._get_extra('_allow_ip') is True:
+            try:
+                IP('{0}/32'.format(value))
+                return
+            except ValueError:
+                pass
+        if self._get_extra('_dom_type') == 'domainname':
+            for dom in value.split('.'):
+                _valid_char(dom)
+        else:
+            _valid_char(value)
 
 
 class EmailOption(DomainnameOption):
@@ -470,6 +485,10 @@ class EmailOption(DomainnameOption):
         if not self.username_re.search(username):
             raise ValueError(_('invalid username in email address'))  # pragma: optional cover
         super(EmailOption, self)._validate(domain)
+        super(EmailOption, self)._second_level_validation(domain, False)
+
+    def _second_level_validation(self, value, warnings_only):
+        pass
 
 
 class URLOption(DomainnameOption):
@@ -503,10 +522,14 @@ class URLOption(DomainnameOption):
                                '65536'))  # pragma: optional cover
         # validate domainname
         super(URLOption, self)._validate(domain)
+        super(URLOption, self)._second_level_validation(domain, False)
         # validate file
         if files is not None and files != '' and not self.path_re.search(files):
             raise ValueError(_('invalid url, must ends with filename'))  # pragma: optional cover
 
+    def _second_level_validation(self, value, warnings_only):
+        pass
+
 
 class UsernameOption(Option):
     __slots__ = tuple()
index eae628e..713e72e 100644 (file)
@@ -338,7 +338,7 @@ class Settings(object):
         return Property(self, self._getproperties(opt, path), opt, path)
 
     def __setitem__(self, opt, value):  # pragma: optional cover
-        raise ValueError('you should only append/remove properties')
+        raise ValueError(_('you should only append/remove properties'))
 
     def reset(self, opt=None, _path=None, all_properties=False):
         if all_properties and (_path or opt):  # pragma: optional cover
index ef11389..d0a8eaa 100644 (file)
@@ -149,8 +149,10 @@ class Values(object):
     def reset(self, opt, path=None):
         if path is None:
             path = opt.impl_getpath(self._getcontext())
+        context = self._getcontext()
+        context.cfgimpl_get_settings().validate_properties(opt, False, True,
+                                                           path)
         if self._p_.hasvalue(path):
-            context = self._getcontext()
             setting = context.cfgimpl_get_settings()
             opt.impl_validate(opt.impl_getdefault(),
                               context, 'validator' in setting)
index d882812..660bee4 100644 (file)
@@ -2,7 +2,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: Tiramisu\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2014-10-25 22:43+CEST\n"
+"POT-Creation-Date: 2014-10-26 09:35+CET\n"
 "PO-Revision-Date: \n"
 "Last-Translator: Emmanuel Garette <egarette@cadoles.com>\n"
 "Language-Team: Tiramisu's team <egarette@cadoles.com>\n"
@@ -30,7 +30,7 @@ msgid "unknown group_type: {0}"
 msgstr "group_type inconnu: {0}"
 
 #: tiramisu/config.py:176 tiramisu/setting.py:321 tiramisu/value.py:54
-#: tiramisu/value.py:541
+#: tiramisu/value.py:555
 msgid "the context does not exist anymore"
 msgstr "le context n'existe plus"
 
@@ -481,47 +481,55 @@ msgstr "allow_ip doit être un booléen"
 msgid "allow_without_dot must be a boolean"
 msgstr "allow_without_dot doit être un booléen"
 
-#: tiramisu/option/option.py:454
+#: tiramisu/option/option.py:428
+msgid "invalid domainname's length (min 2)"
+msgstr "longueur du nom de domaine invalide (minimum 2)"
+
+#: tiramisu/option/option.py:430
+msgid "invalid domainname's length (max {0})"
+msgstr "longueur du nom de domaine invalide (maximum {0})"
+
+#: tiramisu/option/option.py:445
 msgid "invalid domainname, must have dot"
 msgstr "nom de domaine invalide, doit avoir un point"
 
-#: tiramisu/option/option.py:456
+#: tiramisu/option/option.py:447
 msgid "invalid domainname's length (max 255)"
 msgstr "longueur du nom de domaine invalide (maximum {1})"
 
-#: tiramisu/option/option.py:458
-msgid "invalid domainname's length (min 2)"
-msgstr "longueur du nom de domaine invalide (minimum 2)"
+#: tiramisu/option/option.py:457
+msgid "same characters may cause problems"
+msgstr "des caractères peuvent poser problèmes"
 
-#: tiramisu/option/option.py:460
+#: tiramisu/option/option.py:459
 msgid "invalid domainname"
 msgstr "nom de domaine invalide"
 
-#: tiramisu/option/option.py:472
+#: tiramisu/option/option.py:483
 msgid "invalid email address, must contains one @"
 msgstr "adresse email invalide, doit contenir un @"
 
-#: tiramisu/option/option.py:475
+#: tiramisu/option/option.py:486
 msgid "invalid username in email address"
 msgstr "nom d'utilisateur invalide dans une adresse email"
 
-#: tiramisu/option/option.py:487
+#: tiramisu/option/option.py:502
 msgid "invalid url, must start with http:// or https://"
 msgstr "URL invalide, doit démarrer avec http:// ou https://"
 
-#: tiramisu/option/option.py:506
+#: tiramisu/option/option.py:521
 msgid "invalid url, port must be an between 0 and 65536"
 msgstr "URL invalide, port doit être entre 0 et 65536"
 
-#: tiramisu/option/option.py:512
+#: tiramisu/option/option.py:528
 msgid "invalid url, must ends with filename"
 msgstr "URL invalide, doit finir avec un nom de fichier"
 
-#: tiramisu/option/option.py:523
+#: tiramisu/option/option.py:542
 msgid "invalid username"
 msgstr "utilisateur invalide"
 
-#: tiramisu/option/option.py:533
+#: tiramisu/option/option.py:552
 msgid "invalid filename"
 msgstr "nom de fichier invalide"
 
@@ -584,6 +592,10 @@ msgstr ""
 "ne peut ajouter la propriété {0} dans l'option {1}: cette propriété est "
 "calculée"
 
+#: tiramisu/setting.py:341
+msgid "you should only append/remove properties"
+msgstr "pour pouvait seulement ajouter/supprimer des propriétés"
+
 #: tiramisu/setting.py:345
 msgid "opt and all_properties must not be set together in reset"
 msgstr "opt et all_properties ne doit pas être renseigné ensemble dans reset"
@@ -610,7 +622,7 @@ msgstr "tentative d'accès à une option nommée : {0} avec les propriétés {1}
 msgid "permissive must be a tuple"
 msgstr "permissive doit être un tuple"
 
-#: tiramisu/setting.py:500 tiramisu/value.py:370
+#: tiramisu/setting.py:500 tiramisu/value.py:379
 msgid "invalid generic owner {0}"
 msgstr "invalide owner générique {0}"
 
@@ -642,7 +654,7 @@ msgstr "option {0} n'existe pas dans l'espace de stockage {1}"
 msgid "invalid default_multi value {0} for option {1}: {2}"
 msgstr "la valeur default_multi est invalide {0} pour l'option {1} : {2}"
 
-#: tiramisu/storage/dictionary/option.py:131 tiramisu/value.py:427
+#: tiramisu/storage/dictionary/option.py:131 tiramisu/value.py:441
 msgid "information's item not found: {0}"
 msgstr "aucune config spécifiée alors que c'est nécessaire"
 
@@ -694,48 +706,52 @@ msgstr "un espace de stockage dictionary ne peut être persistant"
 msgid "optiondescription has no value"
 msgstr "une optiondescription n'a pas de valeur"
 
-#: tiramisu/value.py:301
+#: tiramisu/value.py:303
 msgid "you should only set value with config"
 msgstr "vous devez seul affecter une valeur avec un config"
 
-#: tiramisu/value.py:377
+#: tiramisu/value.py:356
+msgid "owner only avalaible for an option"
+msgstr "owner seulement possible pour une option"
+
+#: tiramisu/value.py:386
 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:467
+#: tiramisu/value.py:481
 msgid "can force cache only if cache is actived in config"
 msgstr ""
 "peut force la mise en cache seulement si le cache est activé dans la config"
 
-#: tiramisu/value.py:506
+#: tiramisu/value.py:520
 msgid "{0} is already a Multi "
 msgstr "{0} est déjà une Multi"
 
-#: tiramisu/value.py:572
+#: tiramisu/value.py:586
 msgid "cannot append a value on a multi option {0} which is a slave"
 msgstr "ne peut ajouter une valeur sur l'option multi {0} qui est une esclave"
 
-#: tiramisu/value.py:591
+#: tiramisu/value.py:605
 msgid "cannot sort multi option {0} if master or slave"
 msgstr "ne peut trier une option multi {0} pour une maître ou une esclave"
 
-#: tiramisu/value.py:595
+#: tiramisu/value.py:609
 msgid "cmp is not permitted in python v3 or greater"
 msgstr "cmp n'est pas permis en python v3 ou supérieure"
 
-#: tiramisu/value.py:604
+#: tiramisu/value.py:618
 msgid "cannot reverse multi option {0} if master or slave"
 msgstr "ne peut inverser une option multi {0} pour une maître ou une esclave"
 
-#: tiramisu/value.py:612
+#: tiramisu/value.py:626
 msgid "cannot insert multi option {0} if master or slave"
 msgstr "ne peut insérer une option multi {0} pour une maître ou une esclave"
 
-#: tiramisu/value.py:620
+#: tiramisu/value.py:634
 msgid "cannot extend multi option {0} if master or slave"
 msgstr "ne peut étendre une option multi {0} pour une maître ou une esclave"
 
-#: tiramisu/value.py:648
+#: tiramisu/value.py:662
 msgid "cannot pop a value on a multi option {0} which is a slave"
 msgstr "ne peut supprimer une valeur dans l'option multi {0} qui est esclave"
 
index 0e2dcc2..eee861d 100644 (file)
@@ -5,7 +5,7 @@
 msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
-"POT-Creation-Date: 2014-10-25 22:43+CEST\n"
+"POT-Creation-Date: 2014-10-26 09:35+CET\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"
@@ -28,7 +28,7 @@ msgid "unknown group_type: {0}"
 msgstr ""
 
 #: tiramisu/config.py:176 tiramisu/setting.py:321 tiramisu/value.py:54
-#: tiramisu/value.py:541
+#: tiramisu/value.py:555
 msgid "the context does not exist anymore"
 msgstr ""
 
@@ -433,47 +433,55 @@ msgstr ""
 msgid "allow_without_dot must be a boolean"
 msgstr ""
 
-#: tiramisu/option/option.py:454
+#: tiramisu/option/option.py:428
+msgid "invalid domainname's length (min 2)"
+msgstr ""
+
+#: tiramisu/option/option.py:430
+msgid "invalid domainname's length (max {0})"
+msgstr ""
+
+#: tiramisu/option/option.py:445
 msgid "invalid domainname, must have dot"
 msgstr ""
 
-#: tiramisu/option/option.py:456
+#: tiramisu/option/option.py:447
 msgid "invalid domainname's length (max 255)"
 msgstr ""
 
-#: tiramisu/option/option.py:458
-msgid "invalid domainname's length (min 2)"
+#: tiramisu/option/option.py:457
+msgid "same characters may cause problems"
 msgstr ""
 
-#: tiramisu/option/option.py:460
+#: tiramisu/option/option.py:459
 msgid "invalid domainname"
 msgstr ""
 
-#: tiramisu/option/option.py:472
+#: tiramisu/option/option.py:483
 msgid "invalid email address, must contains one @"
 msgstr ""
 
-#: tiramisu/option/option.py:475
+#: tiramisu/option/option.py:486
 msgid "invalid username in email address"
 msgstr ""
 
-#: tiramisu/option/option.py:487
+#: tiramisu/option/option.py:502
 msgid "invalid url, must start with http:// or https://"
 msgstr ""
 
-#: tiramisu/option/option.py:506
+#: tiramisu/option/option.py:521
 msgid "invalid url, port must be an between 0 and 65536"
 msgstr ""
 
-#: tiramisu/option/option.py:512
+#: tiramisu/option/option.py:528
 msgid "invalid url, must ends with filename"
 msgstr ""
 
-#: tiramisu/option/option.py:523
+#: tiramisu/option/option.py:542
 msgid "invalid username"
 msgstr ""
 
-#: tiramisu/option/option.py:533
+#: tiramisu/option/option.py:552
 msgid "invalid filename"
 msgstr ""
 
@@ -533,6 +541,10 @@ msgstr ""
 msgid "cannot append {0} property for option {1}: this property is calculated"
 msgstr ""
 
+#: tiramisu/setting.py:341
+msgid "you should only append/remove properties"
+msgstr ""
+
 #: tiramisu/setting.py:345
 msgid "opt and all_properties must not be set together in reset"
 msgstr ""
@@ -557,7 +569,7 @@ msgstr ""
 msgid "permissive must be a tuple"
 msgstr ""
 
-#: tiramisu/setting.py:500 tiramisu/value.py:370
+#: tiramisu/setting.py:500 tiramisu/value.py:379
 msgid "invalid generic owner {0}"
 msgstr ""
 
@@ -585,7 +597,7 @@ msgstr ""
 msgid "invalid default_multi value {0} for option {1}: {2}"
 msgstr ""
 
-#: tiramisu/storage/dictionary/option.py:131 tiramisu/value.py:427
+#: tiramisu/storage/dictionary/option.py:131 tiramisu/value.py:441
 msgid "information's item not found: {0}"
 msgstr ""
 
@@ -636,47 +648,51 @@ msgstr ""
 msgid "optiondescription has no value"
 msgstr ""
 
-#: tiramisu/value.py:301
+#: tiramisu/value.py:303
 msgid "you should only set value with config"
 msgstr ""
 
-#: tiramisu/value.py:377
+#: tiramisu/value.py:356
+msgid "owner only avalaible for an option"
+msgstr ""
+
+#: tiramisu/value.py:386
 msgid "no value for {0} cannot change owner to {1}"
 msgstr ""
 
-#: tiramisu/value.py:467
+#: tiramisu/value.py:481
 msgid "can force cache only if cache is actived in config"
 msgstr ""
 
-#: tiramisu/value.py:506
+#: tiramisu/value.py:520
 msgid "{0} is already a Multi "
 msgstr ""
 
-#: tiramisu/value.py:572
+#: tiramisu/value.py:586
 msgid "cannot append a value on a multi option {0} which is a slave"
 msgstr ""
 
-#: tiramisu/value.py:591
+#: tiramisu/value.py:605
 msgid "cannot sort multi option {0} if master or slave"
 msgstr ""
 
-#: tiramisu/value.py:595
+#: tiramisu/value.py:609
 msgid "cmp is not permitted in python v3 or greater"
 msgstr ""
 
-#: tiramisu/value.py:604
+#: tiramisu/value.py:618
 msgid "cannot reverse multi option {0} if master or slave"
 msgstr ""
 
-#: tiramisu/value.py:612
+#: tiramisu/value.py:626
 msgid "cannot insert multi option {0} if master or slave"
 msgstr ""
 
-#: tiramisu/value.py:620
+#: tiramisu/value.py:634
 msgid "cannot extend multi option {0} if master or slave"
 msgstr ""
 
-#: tiramisu/value.py:648
+#: tiramisu/value.py:662
 msgid "cannot pop a value on a multi option {0} which is a slave"
 msgstr ""