# -*- coding: UTF-8 -*-
###########################################################################
# Eole NG - 2007
# Copyright Pole de Competence Eole  (Ministere Education - Academie Dijon)
# Licence CeCill  cf /root/LicenceEole.txt
# eole@ac-dijon.fr
#
# creolewrap.py
#
# classe abstraite permettant de gérer les configurations
# creole1 et creole2 dans zephir
#
###########################################################################

import os
import traceback
import json
import base64
from html import unescape
from os import close
from tempfile import mkstemp
from glob import glob
from collections import OrderedDict
from configparser import ConfigParser

from zephir.config import charset, PATH_ZEPHIR, PATH_TEMP, PATH_MODULES, DISTRIBS
from zephir.config import NOBODY_GID
from zephir.config import NOBODY_UID
from functools import cmp_to_key

def check_path_temp():
    if not os.path.isdir(PATH_TEMP):
        os.makedirs(PATH_TEMP)
        if os.getuid() != NOBODY_UID:
            # si lancé par le backend, on force le propriétaire
            # (répertoire utilisé également par l'application web)
            os.chown(PATH_TEMP, NOBODY_UID, NOBODY_GID)

from creole import loader1 as loader4
from creole import loader as loader5
from creole.utils import cmp_int
from creole.var_loader import convert_value as convert_value4
from tiramisu.option import OptionDescription
from tiramisu.error import PropertiesOptionError
from tiramisu.setting import owners as owners2
from tiramisu.config import Config as Config2

class InvalidDataError(Exception):
    pass

class PositionError(Exception):
    pass

def check_hidden(properties):
    """regarde si une famille/variable creole 3 est cachée
    """
    hidden_properties = set(['hidden', 'disabled'])
    if hidden_properties.intersection(properties):
        return True
    return False

def get_load_errors(config):
    """détecte les erreurs stockées à l'import d'un fichier de configuration
    """
    load_errors = {}
    setting = config.cfgimpl_get_settings()
    for path_var, properties in list(setting.get_modified_properties().items()):
        if 'load_error' in properties:
            path = path_var.split('.')
            category_name = config.unwrap_from_path('.'.join(path[0:2])
                                                    ).impl_getdoc().capitalize()
            try:
                varname = config.unwrap_from_path('.'.join(path)).impl_getdoc()
                varid = path[-1]
                msg = "Problème d'import de la variable \"{1}\"({2}) de la catégorie \"{0}\"".format(category_name, varname, varid)
                load_errors[path_var] = msg
            except PropertiesOptionError:
                pass
    return load_errors

def convert_to_creole3(option, value, version):
    if option.impl_is_multi():
        if type(value) != list:
            # options "multi": on met la valeur dans une liste si besoin
            value = [value]
        vals = []
        for val in value:
            if val is None:
                vals.append(val)
            else:
                vals.append(convert_value4(option, val))
        return vals
    elif value is not None:
        return convert_value4(option, value)
    return value

def convert_from_creole3(val):
    if isinstance(val, list):
        if val in (None, []):
            return ['']
        values = []
        for value in list(val):
            if value is None:
                values.append('')
            elif not isinstance(value, str):
                values.append(str(value))
            else:
                values.append(value)
        return values
    else:
        if val is None:
            return ['']
        elif not isinstance(val, str):
            return [str(val)]
        else:
            return [val]

def get_upgrade_infos(config):
    """renvoie les informations d'upgrade d'une configuration (version et erreurs de migration)
    """
    version_info = config.impl_get_information('upgrade', '')
    upgrade_infos = get_load_errors(config)
    return {'upgrade': version_info, 'load_errors': upgrade_infos}

def sort_dicts(dict1, dict2):
    """Compare 2 tuples (chemin, nombre) et renvoi -1 0 ou 1.
    Utile pour trier une liste où le chemin est celui du dictionnaire ajouté et où le nombre est
    l'index d'insertion dans la liste.
    """
    name1 = os.path.basename(dict1[0])
    name2 = os.path.basename(dict2[0])
    if name1 == name2:
        # noms identiques: ordre des répertoires conservé
        return cmp_int(dict1[1], dict2[1])
    elif name1 > name2:
        return 1
    else:
        return -1

def is_package_dir(creoledir):
    """détecte si un répertoire de dictionnaires doit être lu en première
    passe par la librairie Creole3 (dictionnaires du module ou venant de paquets)
    """
    dirname, basename = os.path.split(creoledir)
    return (basename in ('module', 'package', 'var_package')) or \
           (basename == 'dicos' and os.path.dirname(dirname) == PATH_MODULES.rstrip('/'))

def check_store(store_file):
    """retourne le contenu d'un 'store' creole et purge les valeurs incompatibles (#16600)
    Supprime eol_version si détecté
    Si c'est le cas, supprime également les valeurs avec paramètre 'old_format'
    (relicats des anciennes fonctions de migration)
    """
    store = json.loads(open(store_file, 'rb').read())
    if 'eol_version' in store:
        del(store['eol_version'])
        for varname, var_data in list(store.items()):
            if type(var_data) == dict and 'old_format' in var_data:
                del(var_data['old_format'])
        # mise à jour du fichier d'origine
        f_store = open(store_file, 'w')
        f_store.write(json.dump(store))
        f_store.close()
    return store

class ZephirDict:
    """sert de wrapper par dessus les dictionnaires creole1 et creole2
    """
    def __init__(self, dicos=[], confdir='', mode='config', version='creole3', \
                 force_instanciate='non', no_auto_store=False, \
                 eole_version=None, server_version=None, groups=None, separators=None, dico=None):
        """initialise un objet dictionnaire avec la bonne api
        """
        self.version = version
        if self.version == 'creole4':
            self.loader = loader4
        else:
            self.loader = loader5
        if groups is not None:
            self.groups = groups
        if separators is not None:
            self.separators = separators
        else:
            self.separators = {}
        if dico is not None:
            self.dico = dico
        self.multivars = []
        self.autovars = []
        self.confdir = confdir
        self.mode = mode
        self.index = 0
        self.warnings = False
        self.force_instanciate = force_instanciate
        self.no_auto_store = no_auto_store
        if eole_version in DISTRIBS:
            self.release_version = DISTRIBS[eole_version][1]
        else:
            self.release_version = None
        # release actuelle du serveur (utile si migration)
        self.server_version = server_version or eole_version
        self.creoledirs = []
        self.dicos = []
        # chargement de tous les dictionnaires disponibles
        start_list = []
        end_list = []
        dict_files = set()  # Utilisé pour vérifier si les fichiers sont bien uniques
        for idx, creoledir in enumerate(dicos):
            if os.path.isdir(creoledir):
                if is_package_dir(creoledir):
                    # dictionnaires de module / package lus en premier
                    for dict_file in glob(creoledir + '/*.xml'):
                        basename = os.path.basename(dict_file)
                        if basename in dict_files:
                            raise Exception('Un dictionnaire est présent en double sur Zéphir ! Vérifiez sur le '
                                            'serveur Zéphir que les paquets installés par la variante et sur ce '
                                            'serveur ne font pas doublons avec les paquets installés de base (%s).' %
                                            basename)
                        dict_files.add(basename)
                        start_list.append((dict_file, idx))
                else:
                    list_dic = glob(creoledir + '/*.xml')
                    for dict_file in list_dic:
                        basename = os.path.basename(dict_file)
                        if basename in dict_files:
                            raise Exception('Un dictionnaire est présent en double sur Zéphir ! Vérifiez sur le '
                                            'serveur Zéphir que les paquets installés par la variante et sur ce '
                                            'serveur ne font pas doublons avec les paquets installés de base (%s).' %
                                            basename)
                        dict_files.add(basename)
                    list_dic.sort()
                    end_list.extend(list_dic)
        # tri de la liste de départ par nom de fichier / ordre
        start_list.sort(key=cmp_to_key(sort_dicts))
        self.dicos.extend([dict_info[0] for dict_info in start_list])
        self.dicos.extend(end_list)

        if dico is None:
            self.load_values(mode, dicos)
        else:
            self.load_values(mode)

    def get_config_file(self, mode, write=False):
        """calcule le nom du fichier de configuration a utiliser en fonction du mode
        write : si True, indique qu'on cherche à sauvegarder le fichier
                si False, recherche le fichier à lire pour récupérer les valeurs déjà connues
        """
        config_file = ""
        if self.confdir != '':
            # le fichier doit exister
            # dans le cas de creole3, on crée un dictionnaire avec les valeurs sauvegardées
            # (chargements successifs de fichiers de valeurs non gérés)
            serv_file = os.path.join(self.confdir, 'zephir.eol')
            mod_file = os.path.join(self.confdir, 'module.eol')
            var_file = os.path.join(self.confdir, 'dico.eol')
            migration_file = os.path.join(self.confdir, 'migration.eol')
            if mode == 'modif_config' and os.path.isfile(serv_file):
                config_file = serv_file
            elif mode in ['config','modif_config','modif_dico']:
                if os.path.isfile(var_file):
                    config_file = var_file
                elif os.path.isfile(mod_file):
                    config_file = mod_file
            elif mode in ['migration', 'modif_migration'] and os.path.isfile(migration_file):
                config_file = migration_file
        return config_file

    def set_owner(self, owner):
        """spécifique creole3 : initialise le propriétaire d'une valeur
        """
        if self.dico is not None:
            if owner not in dir(owners2):
                owners2.addowner(owner)
            self.dico.cfgimpl_get_settings().setowner(getattr(owners2, owner))

    def check_warnings(self):
        # Désactivation des warnings si besoin (désactivés par défaut)
        if self.warnings == False:
            settings = self.dico.cfgimpl_get_settings()
            if 'warnings' in settings:
                settings.remove('warnings')

    def load_dicos_creole3(self, dicos, from_zephir=False):
        # creole 3: on doit charger tous les dictionnaires en une passe
        self.dico = self.loader.CreoleVarLoader(no_auto_store=self.no_auto_store)
        if self.server_version is None or DISTRIBS[self.server_version][1].startswith('2.4'):
            test_duplicate = False
        else:
            test_duplicate = True
        if from_zephir:
            self.creoledirs = []
            dicos = [base64.decodebytes(dico_zeph.encode()).decode() for dico_zeph in dicos[:-1]]
            # chargement de tous les dictionnaires disponibles
            self.dico.read_string(dicos, 'creole', test_duplicate=test_duplicate)
            self.groups = self.dico.groups
        elif dicos:
            creoledirs = []
            mod_dirs = []
            # regroupement des répertoire module / package / var_package
            for dict_dir in dicos:
                if is_package_dir(dict_dir):
                    mod_dirs.append(dict_dir)
                else:
                    creoledirs.append(dict_dir)
            if mod_dirs:
                creoledirs.insert(0, mod_dirs)
            self.creoledirs = creoledirs
            self.dico.read_dir(creoledirs, 'creole', force_test_duplicate=test_duplicate)
            # stockage des séparateurs
            self.separators = {}
            for sep, data in list(self.dico.separators.items()):
                self.separators[sep] = (data[0], data[1] is not None)
            # stockage des groupes (master/slave)
            self.groups = self.dico.groups
        # après chargement des dictionnaires,
        # on ne conserve que la configuration tiramisu
        self.dico = self.dico.get_config()
        self.dico.read_write()
        # désactivation des propriétés 'hidden'
        # pour permettre l'accès aux variables cachées
        settings = self.dico.cfgimpl_get_settings()
        settings.remove('hidden')
        self.check_warnings()
        # par défaut, on utilise 'zephir' comme owner des valeurs modifiées
        # redéfini dans gen_config si édition au niveau variante ou module
        self.set_owner('zephir')

    def load_dicos_creole5(self, dicos, from_zephir=False):
        # creole 3: on doit charger tous les dictionnaires en une passe
        if from_zephir:
            creoledirs = dicos[:-1]
        else:
            creoledirs = []
            mod_dirs = []
            # regroupement des répertoire module / package / var_package
            for dict_dir in dicos:
                if is_package_dir(dict_dir):
                    mod_dirs.append(dict_dir)
                else:
                    creoledirs.append(dict_dir)
            if mod_dirs:
                creoledirs.insert(0, mod_dirs)
            self.creoledirs = creoledirs
        self.groups, self.separators, self.dico = self.loader.creole_loader(load_values=False, rw=True,
                          owner='zephir', try_upgrade=False, force_dirs=creoledirs, warnings=False,
                          force_dtdfile=None, from_zephir=from_zephir, force_no_save=True)

        # désactivation des propriétés 'hidden'
        # pour permettre l'accès aux variables cachées
        settings = self.dico.cfgimpl_get_settings()
        settings.remove('hidden')
        self.set_owner('zephir')

    def migrate_creole3(self, dicos):
        """fonction de migration d'une configuration creole2 vers creole3
        pour creole3, le fonctionnement est le suivant
        - stockage des valeurs par défaut de module/variante de la version cible dans un dictionnaire python
        - chargement de l'ancien fichier de configuration avec le loader creole (appel auto des fonctions de migration)
          et récupération des valeurs dans un dictionnaire
        - fusion des valeurs par défaut et des valeurs migrées
        - rechargement de la configuration avec ces valeurs
        """
        # fichier des valeurs actuelles
        current_eol = os.path.join(self.confdir, 'zephir.eol')
        migration_eol = os.path.join(self.confdir, 'migration.eol')
        # on essaye de charger les données de module.eol et dico.eol en premier
        def_values = {}
        for path_conf in dicos:
            for conf_file in ('module.eol','dico.eol'):
                fic_defaults = os.path.join(os.path.dirname(path_conf),conf_file)
                if os.path.isfile(fic_defaults):
                    try:
                        new_vals = check_store(fic_defaults)
                        def_values.update(new_vals)
                    except:
                        pass
        version = self.release_version
        # chargement du fichier actuel
        store_values = self.loader.load_store(self.dico, current_eol)
        def_values.update(store_values)
        self.dico.impl_set_information('eol_version', version)
        # application des migrations automatiques de valeurs
        settings = self.dico.cfgimpl_get_settings()
        settings.remove('disabled')
        settings.remove('frozen')
        self.loader.load_config_store(self.dico,
                                      def_values,
                                      unset_default=False,
                                      force_instanciate='non',
                                      current_eol_version=self.release_version,
                                      remove_unknown_vars=False,
                                      try_upgrade=True)
        # si load_error True  : erreur lors de l'upgrade des valeurs !!
        self.dico.impl_set_information('upgrade', version)
        settings.append('frozen')
        settings.append('disabled')

    def _load_values_creole3(self, mode, dicos, config_file):
        if dicos:
            if self.version == "creole5":
                self.load_dicos_creole5(dicos)
            else:
                self.load_dicos_creole3(dicos)
        # if not config_file and not dicos and self.creoledirs:
        elif self.creoledirs:
            # création d'un nouvelle configuration depuis la description existante si
            # retour aux valeurs par défaut quand les dictionnaires sont déjà chargés
            cfg_descr = self.dico.cfgimpl_get_description()
            unknown_values = {}
            unknown_values.update(self.dico.impl_get_information('unknown_options', {}))
            self.dico = Config2(cfg_descr)
            self.dico.impl_set_information('creole', config_file)
            self.dico.impl_set_information('unknown_options', unknown_values)
            self.dico.read_write()
            settings = self.dico.cfgimpl_get_settings()
            settings.remove('hidden')
            _modes = list(self.loader.modes_level)
            _modes.append('hidden')
            settings.setpermissive(tuple(_modes))
            self.check_warnings()
        # chargement du fichier de valeurs
        if mode == 'migration':
            self.migrate_creole3(dicos)
        elif config_file:
            store_values = {}
            config_files = [config_file]
            # cas particulier pour creole3. Dans le cas de chargement des valeurs par défaut
            # d'une variante, on charge aussi les valeurs au niveau module
            if os.path.basename(config_file) == 'dico.eol':
                config_files.insert(0, os.path.join(os.path.dirname(config_file), 'module.eol'))
            for conf_file in config_files:
                if os.path.isfile(conf_file):
                    try:
                        store_values.update(check_store(conf_file))
                    except:
                        pass
            # on désactive les tests sur frozen pour pouvoir recharger les valeurs
            settings = self.dico.cfgimpl_get_settings()
            settings.remove('frozen')
            settings.remove('disabled')
            # si le serveur est déjà enregistré, on considère qu'il est instancié
            # (les variables auto_freeze ne sont plus modifiables)
            # valeurs passées sous forme de dictionnaires
            self.loader.config_load_store(self.dico, 'creole', store_values, force_instanciate=self.force_instanciate)
            settings.append('disabled')
            settings.append('frozen')
        if hasattr(self, 'dico'):
            self._update_vars()

    def load_values(self, mode, dicos=[], config_file=None):
        """ lecture des valeurs actuelles selon le mode demandé
        """
        if config_file is None:
            config_file = self.get_config_file(mode)
        self._load_values_creole3(mode, dicos, config_file)
        self.mode = mode

    def get_help(self, name, family=False):
        """Récupère l'aide de la variable
        - définie dans une balise help pour creole2 (chaine vide si non défini)
        - pour creole 1, on renvoie une chaine vide
        """
        try:
            if family:
                opt = self.dico.unwrap_from_path('creole.' + name)
            else:
                opt = self.dico.vars[self.dico.liste_vars[name]]
        except:
            opt = None
        if opt: return opt.impl_getdoc()
        return ""

    def init_from_zephir(self, dicos, load_error=False, force_load_owner=None, warnings=False, eole_version=None):
        """charge les dictionnaire depuis une liste de chaines xml
        @param dicos: contenu des dictionnaires à implémenter + valeurs actuelles en fin de liste
        @param load_error: passer à True pour creole3 dans le cas où la configuration doit être upgradée
        """
        # chargement depuis des valeurs envoyées, on ne sait pas dans quel mode on est
        self.mode = None
        self.warnings = warnings
        upgrade_infos = None
        # redéfinition du n° de release si passé en paramêtre
        if eole_version in DISTRIBS:
            self.release_version = DISTRIBS[eole_version][1]
        if type(dicos[-1] == dict) and 'load_errors' in dicos[-1]:
            upgrade_infos = dicos[-1]
            dicos = dicos[:-1]
        if self.dicos == []:
            # Les dictionnaires n'ont pas été chargés précédemment, on utilise ceux fournis
            if self.version == "creole5":
                self.load_dicos_creole5(dicos, True)
            else:
                self.load_dicos_creole3(dicos, True)
        # prise en compte du dictionnaire de valeurs
        values = eval(unescape(dicos[-1]))
        #on reinitialise aux valeurs par defaut
        cfg_descr = self.dico.cfgimpl_get_description()
        force_store_values =  self.dico.impl_get_information('force_store_values', [])
        self.dico = Config2(cfg_descr)
        self.dico.impl_set_information('force_store_values', force_store_values)
        _modes = list(self.loader.modes_level)
        _modes.append('hidden')
        settings = self.dico.cfgimpl_get_settings()
        settings.setpermissive(tuple(_modes))
        if not load_error:
            self.loader.config_load_store(self.dico, 'creole', values,
                              force_instanciate=self.force_instanciate,
                              force_load_owner=force_load_owner)
            if upgrade_infos:
                self.dico.impl_set_information('upgrade', upgrade_infos['upgrade'])
                # réinjection des erreurs dans les variables
                for opt_path, error in list(upgrade_infos['load_errors'].items()):
                    opt = self.dico.unwrap_from_path(opt_path)
                    settings[opt].append('load_error')
                    path_split = opt_path.split('.')
                    family_option = self.dico.unwrap_from_path(path_split[0] + '.' + path_split[1])
                    settings.setpermissive(tuple(self.loader.modes_level), opt=family_option)
                    if len(path_split) == 4:
                        parent_option = self.dico.unwrap_from_path(path_split[0] + '.' + path_split[1] + '.' + path_split[2])
                        settings.setpermissive(tuple(self.loader.modes_level), opt=parent_option)
                    settings.setpermissive(tuple(self.loader.modes_level), opt=opt)
            elif eole_version and self.server_version != eole_version:
                # trying to upgrade values if necessary
                self.loader.load_values(self.dico, force_load_owner=force_load_owner, current_eol_version=DISTRIBS[eole_version][1])
        self.dico.read_write()
        settings.remove('hidden')
        self.check_warnings()
        if len(dicos) > 1:
            # si dictionnaires réinitialisés,
            # on réinitialise les données de structure
            self._update_vars()

    def _update_vars(self):
        """regénère les structures après chargement des dictionnaires
        """
        # création d'un liste pour ordonner les variables
        self.index = 0
        self.vars = []
        # création de self.liste_vars
        self.liste_vars = {}
        ind = 0
        # dictionnaire pour retrouver le chemin d'une variable donnée
        self.var_paths = {}
        # liste des familles ordonnée
        self.families = []
        self.expert_families = []
        # lecture du cache ordonné des variables
        # on n'a pas besoin de conserver la liste des variables multi/auto
        descr_cache = self.dico.cfgimpl_get_description()._cache_paths
        for cache_index, descr in enumerate(descr_cache[0]):
            current_mode = 'normal'
            if isinstance(descr, OptionDescription):
                if getattr(descr, '_group_type', '') == 'family':
                    # vérification du mode expert au niveau de la famille
                    if 'expert' in descr._properties:
                        self.expert_families.append(descr)
                        current_mode = 'expert'
                    else:
                        self.families.append(descr)
            else:
                current_path = descr_cache[1][cache_index]
                self.vars.append(descr)
                self.liste_vars[descr._name] = ind
                self.var_paths[descr._name] = (current_path, current_mode)
                ind += 1

    def get_value(self, var):
        # Si la variable est 'disabled', on ne peut pas accéder à la valeur
        try:
            val = getattr(self.dico, self.var_paths[var][0]) or ''
        except PropertiesOptionError as e:
            val = ''
        return convert_from_creole3(val)

    def get_first(self):
        """retourne les paramètres de la variable nom,valeur,libelle,variable cachée
        """
        self.index = 0
        try:
            return self._get_var_details()
        except PropertiesOptionError as e:
            # pour creole3, on gère le cas ou la première
            # variable serait désactivée
            return self.get_next()

    def get_next(self):
        prev_index = self.index
        self.index = self.index + 1
        try:
            return self._get_var_details()
        except PropertiesOptionError as e:
            # variable non accessible, on passe à la suivante
            return self.get_next()
        except (KeyError, IndexError) as e:
            self.index = prev_index
            raise PositionError('fin de la liste des variables')

    def get_var(self, varname=None, default=False):
        """retourne les paramètres d'une variable
        """
        try:
            return self._get_var_details(varname, default)
        except PropertiesOptionError as e:
            # FIXME variable disabled, à gérer différement ?
            return ['']

    def _get_var_details(self, varname=None, default=False):
        """adapte la représentation des variables
        """
        # récupération des infos
        # creole3 : on  ne récupère pas toutes les informations
        # (nom et description seulement). Le formulaire de saisie
        # est géré indépendament
        if varname != None:
            self.index = self.liste_vars[varname]
        opt = self.vars[self.index]
        # adaptation du mode
        if 'expert' in opt._properties:
            mode = 'expert'
        else:
            # pas de distinction entre basic et normal
            mode = 'normal'
        hidden = check_hidden(opt._properties)
        obligatoire = 'mandatory' in opt._properties
        mime = 'string'
        val = getattr(self.dico, self.var_paths[opt._name][0])
        if not val:
            if isinstance(val, list):
                val = ['']
            else:
                val = ''
        doc = opt.impl_getdoc()
        if doc is None:
            doc = opt._name
        data = opt._name, val, doc, hidden, obligatoire, mime, mode
        return data

    def get_prec_value(self, varname):
        """ renvoie la valeur précédente """
        try:
            value = self.dico.get_prec_value(varname)
        except:
            value = ''
        return value

    def get_default_value(self, varname):
        """ renvoie la valeur par défaut """
        try:
            value = self.vars[self.liste_vars[varname]].impl_getdefault()
        except:
            value = ''
        return value

    def set_value(self, value, invisible=False, force=False):
        try:
            option = self.vars[self.index]
            varname = option._name
            # reprise du fonctionnement de eole-genconfig (conversion + mode permissive)
            value = convert_to_creole3(option, value, self.version)
            self.dico.cfgimpl_get_settings().setpermissive(tuple(self.loader.modes_level), opt=option)
            setattr(self.dico, self.var_paths[varname][0], value)
            # rechargement des familles/variables accessibles
            self._update_vars()
        except Exception as e:
            traceback.print_exc()
            raise InvalidDataError(e.args[0])

    def save(self, eol_file='/dev/null', force=False, encode=False):
        # vérifier eole_file : config.impl_get_information(namespace)
        check_mandatory=not force
        if eol_file and eol_file != '/dev/null':
            # Vérification de la destination d'écriture
            if not (eol_file.startswith(PATH_ZEPHIR) or eol_file.startswith(PATH_TEMP)):
                print("ERR : écriture dans {0}".format(eol_file))
                raise IOError("Destination invalide pour le fichier de configuration: {0}".format(eol_file))
            if eol_file.startswith(PATH_TEMP):
                check_path_temp()
            self.loader.config_save_values(self.dico, 'creole', eol_file=eol_file, check_mandatory=check_mandatory, eol_version=self.release_version)
        data = [str(self.loader.config_get_values(self.dico, 'creole', check_mandatory))]
        return data

    def get_dict(self):
        # on envoie le dictionnaire sous forme de fichier texte
        data = []
        for dic in self.dicos:
            f = open(dic)
            content = f.read()
            f.close()
            data.append(base64.encodebytes(content.encode()).decode())
        val_store = self.loader.config_get_values(self.dico, 'creole', check_mandatory=False, ignore_autofreeze=True)
        data.append(str(val_store))
        return data

    def get_menu(self, expert=False):
        """retourne les familles et les variables dans l'ordre de saisie
        @param expert: mode de saisie (non expert par défaut)
        @return: renvoie une liste ordonnée de familles [famille, liste_vars]
        """
        families = []
        expert_families = []
        menu = []
        for family in self.families:
            childs = family._children[0]
            hidden = check_hidden(self.dico.unwrap_from_path('creole.%s' % family._name)._properties)
            families.append([family._name, hidden, childs])
        if expert:
            for family in self.expert_families:
                childs = family._children[0]
                hidden = check_hidden(self.dico.unwrap_from_path('creole.%s' % family._name)._properties)
                expert_families.append([family._name, hidden, childs])
        return menu

    def parsedico(self, separator=', '):
        # renvoie la configuration sous forme d'un dictionnaire {variable:valeur}
        data = {}
        # passage en mode lecture pour activer les 'settings de base'
        data_dict = self.dico.make_dict(flatten=True)
        # mise en place des séparateurs
        for varname, vals in list(data_dict.items()):
            vals = convert_from_creole3(vals)
            data[varname] = separator.join(vals)
        return data
