web-dev-qa-db-fra.com

Création d'un fichier de configuration simple et d'un analyseur syntaxique en C++

J'essaie de créer un fichier de configuration simple qui ressemble à ceci

url = http://mysite.com
file = main.exe
true = 0

lors de l'exécution du programme, j'aimerais qu'il charge les paramètres de configuration dans les variables de programme répertoriées ci-dessous.

string url, file;
bool true_false;

J'ai fait des recherches et ceci link semblait aider (message de nucléon) mais je n'arrive pas à le faire fonctionner et c'est trop compliqué à comprendre de ma part. Y a-t-il un moyen simple de faire cela? Je peux charger le fichier en utilisant ifstream mais c'est aussi loin que je peux obtenir moi-même. Merci!

45
llk

En général, il est plus facile d'analyser de tels fichiers de configuration typiques en deux étapes: lisez d'abord les lignes, puis analysez-les un par un.
En C++, les lignes peuvent être lues à partir d’un flux à l’aide de std::getline(). Alors que par défaut, il lira jusqu'au prochain '\n' (qu'il consommera mais ne renverra pas), vous pouvez également lui transmettre un autre délimiteur, ce qui en fait un bon candidat pour la lecture de caractères, comme = dans votre exemple. 

Pour simplifier, les éléments suivants supposent que le = est pas entouré par des espaces. Si vous souhaitez autoriser les espaces blancs à ces positions, vous devrez placer is >> std::ws de manière stratégique avant de lire la valeur et supprimer les espaces finaux des clés. Cependant, IMO, le peu de souplesse supplémentaire dans la syntaxe ne vaut pas la peine d'être un lecteur de fichier de configuration. 

const char config[] = "url=http://example.com\n"
                      "file=main.exe\n"
                      "true=0";

std::istringstream is_file(config);

std::string line;
while( std::getline(is_file, line) )
{
  std::istringstream is_line(line);
  std::string key;
  if( std::getline(is_line, key, '=') )
  {
    std::string value;
    if( std::getline(is_line, value) ) 
      store_line(key, value);
  }
}

(L'ajout du traitement des erreurs est laissé au lecteur sous la forme d'un exercice.)

42
sbi

Comme d'autres l'ont souligné, l'utilisation d'une bibliothèque existante d'analyseur de fichiers de configuration nécessitera probablement moins de travail que de réinventer la roue.

Par exemple, si vous décidez d'utiliser la bibliothèque Config4Cpp (que je gère), la syntaxe de votre fichier de configuration sera légèrement différente (mettez des guillemets autour des valeurs et terminez les instructions d'affectation avec un point-virgule), comme illustré dans l'exemple ci-dessous. :

# File: someFile.cfg
url = "http://mysite.com";
file = "main.exe";
true_false = "true";

Le programme suivant analyse le fichier de configuration ci-dessus, copie les valeurs souhaitées dans des variables et les imprime:

#include <config4cpp/Configuration.h>
#include <iostream>
using namespace config4cpp;
using namespace std;

int main(int argc, char ** argv)
{
    Configuration *  cfg = Configuration::create();
    const char *     scope = "";
    const char *     configFile = "someFile.cfg";
    const char *     url;
    const char *     file;
    bool             true_false;

    try {
        cfg->parse(configFile);
        url        = cfg->lookupString(scope, "url");
        file       = cfg->lookupString(scope, "file");
        true_false = cfg->lookupBoolean(scope, "true_false");
    } catch(const ConfigurationException & ex) {
        cerr << ex.c_str() << endl;
        cfg->destroy();
        return 1;
    }
    cout << "url=" << url << "; file=" << file
         << "; true_false=" << true_false
         << endl;
    cfg->destroy();
    return 0;
}

Config4Cpp website fournit une documentation complète, mais il suffit de lire les chapitres 2 et 3 du "Guide de démarrage" pour répondre à vos besoins.

32
Ciaran McHale

Une approche naïve pourrait ressembler à ceci:

#include <map>
#include <sstream>
#include <stdexcept>
#include <string>

std::map<std::string, std::string> options; // global?

void parse(std::istream & cfgfile)
{
    for (std::string line; std::getline(cfgfile, line); )
    {
        std::istringstream iss(line);
        std::string id, eq, val;

        bool error = false;

        if (!(iss >> id))
        {
            error = true;
        }
        else if (id[0] == '#')
        {
            continue;
        }
        else if (!(iss >> eq >> val >> std::ws) || eq != "=" || iss.get() != EOF)
        {
            error = true;
        }

        if (error)
        {
            // do something appropriate: throw, skip, warn, etc.
        }
        else
        {
            options[id] = val;
        }
    }
}

Vous pouvez désormais accéder à chaque valeur d’option de la carte globale options n’importe où dans votre programme. Si vous voulez la castability, vous pouvez transformer le type mappé en boost::variant.

13
Kerrek SB

libconfig est très simple. De plus, il utilise une notation pseudo json pour une meilleure lisibilité.

Facile à installer sur Ubuntu: Sudo apt-get install libconfig++8-dev

et lien: -lconfig++

11
user1382306

J'ai récemment effectué une recherche dans les bibliothèques d'analyse de configuration pour mon projet et j'ai trouvé ces bibliothèques:

3
Ivan Samygin

Pourquoi ne pas formater votre configuration en JSON et utiliser une bibliothèque telle que jsoncpp

par exemple.

{"url": "http://mysite dot com",
"file": "main.exe",
"true": 0}

Vous pouvez ensuite le lire dans des variables nommées, ou même tout stocker dans un std :: map, etc. Ce dernier signifie que vous pouvez ajouter des options sans avoir à changer ni à recompiler votre analyseur de configuration.

3
patmanpato

Pourquoi ne pas essayer quelque chose de simple et lisible par l'homme, comme JSON (ou XML)? 

Il existe de nombreuses implémentations open source prédéfinies de JSON (ou XML) pour C++ - je voudrais utiliser l'une d'entre elles.

Et si vous voulez quelque chose de plus "binaire" - essayez BJSON ou BSON :)

3
yosh kemu

Voici un moyen simple de contourner l’espace vide entre le signe '=' et les données, dans le fichier de configuration. Attribuez-le au flux sortant depuis l'emplacement après le signe '=' et lors de la lecture de celui-ci, tout espace blanc au début est ignoré.

Remarque: lorsque vous utilisez un istringstream dans une boucle, assurez-vous d’appeler clear () avant de lui affecter une nouvelle chaîne.

//config.txt
//Input name = image1.png
//Num. of rows = 100
//Num. of cols = 150

std::string ipName;
int nR, nC;

std::ifstream fin("config.txt");
std::string line;
std::istringstream sin;

while (std::getline(fin, line)) {
 sin.str(line.substr(line.find("=")+1));
 if (line.find("Input name") != std::string::npos) {
  std::cout<<"Input name "<<sin.str()<<std::endl;
  sin >> ipName;
 }
 else if (line.find("Num. of rows") != std::string::npos) {
  sin >> nR;
 }
 else if (line.find("Num. of cols") != std::string::npos) {
  sin >> nC;
 }
 sin.clear();
}
1
haripkannan

Je cherchais quelque chose qui fonctionne comme le module python ConfigParser et j'ai trouvé ceci: https://github.com/jtilly/inih

Ceci est un en-tête uniquement en version C++ de inih.

inih (INI non inventé ici) est un analyseur de fichier .INI simple écrit en C. Ce n'est que quelques pages de code, et il a été conçu pour être petit et simple, il est donc bon pour les systèmes embarqués. C'est aussi plus ou moins compatible avec le style de fichiers .INI ConfigParser de Python, y compris la syntaxe sur plusieurs lignes de style RFC 822 et les entrées nom: valeur.

1
AaronS