Articles tagués “formatage

Formater du texte en C++ avec {fmt}

Formater du texte en C++, c’est pas ultra fun… Il y a bien les « bons » vieux reliquats du C avec les fonctions genre std::printf() et les méthodes un peu plus modernes comme std::ostringstream. C’est pas génial comparer à string.format() de Python En regardant le code de spdlog( une bibliothèque de logging qui semble très bien mais qui malheureusement n’était pas compatible avec mes contraintes), j’ai découvert la bibliothèque {fmt}. C’est elle qui sert de back-end à spdlog pour formater les messages. Si vous voulez essayer {fmt}, voici le mode opératoire !

Builder la bibliothèque

Tout le code est sur GitHub, est placé sous licence BSD et se builde avec CMake. Le mode opératoire est donc très classique :

$ git clone https://github.com/fmtlib/fmt.git
$ cd fmt
p$ mkdir build
$ cd build
$ cmake ..
[...]
$ make all
Scanning dependencies of target fmt
[  2%] Building CXX object CMakeFiles/fmt.dir/src/format.cc.o
[  4%] Building CXX object CMakeFiles/fmt.dir/src/posix.cc.o
[  7%] Linking CXX static library libfmt.a
[  7%] Built target fmt
Scanning dependencies of target gmock
[  9%] Building CXX object test/CMakeFiles/gmock.dir/gmock-gtest-all.cc.o
[ 12%] Linking CXX static library libgmock.a
[ 12%] Built target gmock
Scanning dependencies of target test-main
[ 14%] Building CXX object test/CMakeFiles/test-main.dir/test-main.cc.o
[ 17%] Building CXX object test/CMakeFiles/test-main.dir/gtest-extra.cc.o
[ 19%] Building CXX object test/CMakeFiles/test-main.dir/util.cc.o
[ 21%] Linking CXX static library libtest-main.a
[ 21%] Built target test-main
Scanning dependencies of target time-test
[ 24%] Building CXX object test/CMakeFiles/time-test.dir/time-test.cc.o
[ 26%] Linking CXX executable ../bin/time-test
[...]
[100%] Linking CXX executable ../bin/util-test
[100%] Built target util-test

$ make test
Running tests...
Test project /home/pgradot/Documents/GitHub/fmt/build
      Start  1: assert-test
 1/11 Test  #1: assert-test ......................   Passed    0.02 sec
      Start  2: gtest-extra-test
 2/11 Test  #2: gtest-extra-test .................   Passed    0.03 sec
      Start  3: format-test
[...]
      Start 10: posix-mock-test
10/11 Test #10: posix-mock-test ..................   Passed    0.20 sec
      Start 11: posix-test
11/11 Test #11: posix-test .......................   Passed    4.85 sec

100% tests passed, 0 tests failed out of 11

Total Test time (real) =   5.84 sec


$ sudo make install
[  7%] Built target fmt
[...]
[100%] Built target format-impl-test
Install the project...
-- Install configuration: "Release"
-- Installing: /usr/local/lib/cmake/fmt/fmt-config.cmake
-- Installing: /usr/local/lib/cmake/fmt/fmt-config-version.cmake
-- Installing: /usr/local/lib/cmake/fmt/fmt-targets.cmake
-- Installing: /usr/local/lib/cmake/fmt/fmt-targets-release.cmake
-- Installing: /usr/local/lib/libfmt.a
-- Installing: /usr/local/include/fmt/core.h
-- Installing: /usr/local/include/fmt/format.h
-- Installing: /usr/local/include/fmt/format-inl.h
-- Installing: /usr/local/include/fmt/locale.h
-- Installing: /usr/local/include/fmt/ostream.h
-- Installing: /usr/local/include/fmt/printf.h
-- Installing: /usr/local/include/fmt/time.h
-- Installing: /usr/local/include/fmt/posix.h

J’ai volontairement raccourci la sortie de plusieurs commandes (vous avez sans doute vu les […]). Vous n’êtes pas obligé de faire make test mais c’est sympa de vérifier que notre bibliothèque s’est correctement compilée. On voit que l’installation a copié dans des dossiers classiques la bibliothèque statique libfmt.a ainsi que les nombreux headers dont nous aurons besoin pour l’utiliser dans notre code. Vous pourriez ne pas l’installer et récupérer les fichiers pour les mettre dans votre projet ou encore utiliser directement le CMake de {fmt} comme un sous-CMake de votre projet.

Petits essais

J’ai repris quelques lignes données dans le README du GitHub et j’ai écrit une petite fonction log() puisque c’est un peu pour ça que je me suis intéressé à {fmt} :

#include 
#include 
#include 

namespace pgt
{
template 
void log(const char* format, Args&& ... args)
{
	try
	{
		auto now = std::time(nullptr);
		auto timestamp = std::string(std::ctime(&now));
		std::replace(timestamp.begin(), timestamp.end(), '\n', '\0');

		auto message = fmt::format(format, args...);
		fmt::print("[{}] {}\n", timestamp, message);
	}
	catch(const fmt::format_error& e)
	{
		fmt::print("Invalid log message: {}\n", e.what());
	}
}
}

int main()
{
	// Exemples de GitHub
	fmt::print("Hello, {}!\n", "world");
	fmt::printf("Hello, %s!\n", "world");
	std::string s = fmt::format("{0}{1}{0}\n", "abra", "cad");
	fmt::print(s);

	// Log
	int speed = 1200;
	pgt::log("Speed = {} rpm - Temperature = {}°C", speed, 42);
	pgt::log("Setpoint = {}");
}

On compile et on lance le programme :

$ g++ -Wall -Wextra -std=c++11 main.cpp -lfmt && ./a.out
Hello, world!
Hello, world!
abracadabra
[Fri May 11 11:59:13 2018] Speed = 1200 rpm - Temperature = 42°C
Invalid log message: argument index out of range

Notes :

  1. C’est pas une super bonne idée de faire une fonction qui s’appelle log dans le namespace global, elle risque d’entrer en conflit avec std::log, qui dans mon cas était disponible (le test a été fait avec un gcc 5.4). Ainsi, un appel à log(42) compilait sans erreur alors que pgt::log(42) génère bien un message d’erreur.
  2. Il y a moyen de faire encore mieux pour formater le timestamp des logs mais mon vieux compilateur semblait perdu…
  3. Si on intervertit main.cpp et -lfmt dans la ligne de commande, on se mange une palanquée d’erreurs de link.
  4. Oui, un formatage peut rater et lancer des exceptions !

Personnellement, je trouve ça bien sympa !

Pour aller plus loin, vous pouvez regarder la documentation de l’API ou approfondir sur la syntaxe du formatage,

C’est tout pour aujourd’hui 🙂


Utiliser les save actions d’Eclipse

Certains disent du mal d’Eclipse. On dira ce qu’on veut, cet IDE propose énormément de fonctionnalités et vos collègues auront toujours de nouvelles astuces à vous faire découvrir. Une astuce que j’aime partager est l’utilisation des save actions pour automatiser des actions « pénibles » par un simple appui sur CTRL+S. Pour les activer, il suffit d’aller dans Window / Preferences… et de taper « save actions » dans le champ de recherche. Vous constaterez que chaque éditeur est configuré séparément. Pour l’exemple, je m’intéresse ici à l’éditeur Java car c’est l’éditeur que j’utilise principalement dans Eclipse. Généralement, quand je démarre un nouveau projet, je travaille comme ça :

save actions - 1 - window pref

S’il s’agit d’un projet existant, alors je choisis de ne formater que les lignes éditées. En effet, on risque de provoquer énormément de changements et donc rendre impossible la comparaison des versions du fichier dans un système de contrôle de sources. Cela ne devrait pas être le cas dans un projet bien organisé car tout le monde devrait travailler le même formateur de code et formater son code avant chaque commit dans le gestionnaire de versions des sources, mais la réalité est souvent différente.

Pour vous montrer l’intérêt, voici un bout de code taper « à la main », sans utiliser l’auto-complétion (car l’auto-complétion permet de rajouter automatiquement les imports) :

save actions - 2 - before

Si je l’enregistre en activant les save actions comme montré dans la première capture d’écran, le code ressemble à ça :

save actions - 3 - code after

C’est pas mieux ? La Javadoc a été remise en forme, les espaces et tabulations ont été correctement ajoutées ou enlevées (y compris en fin de ligne, comme le montre la ligne en bleu), les accolades se mettent aux bons endroits, les imports manquant se rajoutent et résolvent les erreurs de compilations.

Vous gagnerez beaucoup de temps en utilisant les save actions. Vous pouvez être sûr de toujours avoir un code formaté correctement, conformément aux paramètres du formateur courant de l’éditeur Java. Dans la première capture d’écran, vous pouvez voir que la dernière checkbox est « Additional actions ». Vous aurez accès à encore plus d’actions !

CTRL+S, c’est la vie, et pas seulement pour ne pas perdre ce qu’on vient de faire !


Concevoir un site comme celui-ci avec WordPress.com
Commencer