{"id":4205,"date":"2014-10-20T04:54:55","date_gmt":"2014-10-20T03:54:55","guid":{"rendered":"http:\/\/www.blaess.fr\/christophe\/?p=4205"},"modified":"2014-10-20T08:41:14","modified_gmt":"2014-10-20T07:41:14","slug":"extraction-des-fonctions-dun-fichier-c","status":"publish","type":"post","link":"https:\/\/www.blaess.fr\/christophe\/2014\/10\/20\/extraction-des-fonctions-dun-fichier-c\/","title":{"rendered":"Extraction des fonctions d&rsquo;un fichier C"},"content":{"rendered":"<p style=\"text-align: justify;\"><a href=\"http:\/\/www.blaess.fr\/christophe\/2014\/10\/20\/extraction-des-fonctions-dun-fichier-c\/script-shell\/\"><img loading=\"lazy\" decoding=\"async\" class=\"alignright wp-image-4221 size-full\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2014\/10\/Script-Shell.gif\" alt=\"Extraction des fonctions d'un fichier C\" width=\"160\" height=\"120\" \/><\/a>J&rsquo;ai eu besoin il y a quelques jours de comparer deux versions d&rsquo;un m\u00eame fichier C apr\u00e8s qu&rsquo;il a subi de nombreuses modifications. La structure du fichier et le contenu ayant beaucoup boug\u00e9, les outils habituels (\u00e9diteurs, <code>diff<\/code>, <code>meld<\/code>, etc.) ne pouvaient me venir en aide. J&rsquo;avais besoin d&rsquo;extraire chaque fonction du programme pour qu&rsquo;elle se trouve dans un fichier s\u00e9par\u00e9. Cela m&rsquo;a amen\u00e9 \u00e0 \u00e9crire un petit script shell qui pourra peut-\u00eatre s&rsquo;av\u00e9rer utile \u00e0 d&rsquo;autres&#8230;<\/p>\n<p>\n<!--more-->\n<\/p>\n<p style=\"text-align: justify;\">Le fichier en question provenait d&rsquo;un projet client ancien, dont le code avait \u00e9t\u00e9 \u00e9crit de mani\u00e8re incr\u00e9mentale, avait servi pour du d\u00e9bogage, de la mise au point d&rsquo;algorithmes, etc. La premi\u00e8re version du fichier devenait illisible et impossible \u00e0 maintenir sereinement. Il a donc \u00e9t\u00e9 d\u00e9cid\u00e9 de proc\u00e9der \u00e0 un r\u00e9usinage pour le rendre plus conforme aux standards logiciels actuels.<\/p>\n<p style=\"text-align: justify;\">Cette phase de <em>refactoring<\/em> a consist\u00e9 en plusieurs op\u00e9rations. Entre autres<\/p>\n<ul>\n<li style=\"text-align: justify;\">suppression de code en commentaire ou dans des portions inaccessibles (comme <code>#ifdef 0<\/code> \/ <code>#endif<\/code>)<\/li>\n<li style=\"text-align: justify;\">suppression de code mort (routines jamais appel\u00e9es)<\/li>\n<li style=\"text-align: justify;\">suppression des variables globales (plusieurs centaines&nbsp;!) inutilis\u00e9es (plusieurs dizaines&nbsp;!)<\/li>\n<li style=\"text-align: justify;\">r\u00e9int\u00e9gration du maximum de variables globales en variables statiques ou automatiques<\/li>\n<li style=\"text-align: justify;\">renommage de variables et de constantes<\/li>\n<li style=\"text-align: justify;\">r\u00e9organisation globale du fichier pour contenir des blocs coh\u00e9rents (d\u00e9finitions des macros, des types, des fonctions priv\u00e9es, des variables globales, impl\u00e9mentations des fonctions publiques puis des fonctions priv\u00e9es)<\/li>\n<li style=\"text-align: justify;\">r\u00e9\u00e9criture du code pour correspondre \u00e0 des normes de lisibilit\u00e9 actuelles. Personnellement j&rsquo;ai adopt\u00e9 le standard de <a title=\"http:\/\/www.busybox.net\/\" href=\"http:\/\/www.busybox.net\/\" target=\"_blank\">Busybox<\/a> (voir la description dans son fichier <code><a title=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2014-10-20\/style-guide.txt\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2014-10-20\/style-guide.txt\" target=\"_blank\">docs\/style-guide.txt<\/a><\/code>).<\/li>\n<\/ul>\n<p style=\"text-align: justify;\">Durant les op\u00e9rations manuelles de ce <em>refactoring<\/em> (qui comprenait plusieurs dizaines de fichiers, chacun de plusieurs milliers de lignes de code), une erreur a \u00e9t\u00e9 commise dont le d\u00e9bogage s&rsquo;av\u00e9rait complexe. J&rsquo;ai donc d\u00e9cid\u00e9 de comparer visuellement toutes les modifications, fonction par fonction du module incrimin\u00e9.<\/p>\n<p style=\"text-align: justify;\">Il existe un outil tr\u00e8s agr\u00e9able pour comparer deux versions d&rsquo;un m\u00eame fichier&nbsp;: il s&rsquo;agit de <a title=\"http:\/\/meldmerge.org\/\" href=\"http:\/\/meldmerge.org\/\" target=\"_blank\">Meld<\/a>.Toutefois, lorsque la structure compl\u00e8te du fichier a \u00e9t\u00e9 modifi\u00e9e, il devient difficilement utilisable. La solution la plus simple pour moi consistait \u00e0 exploser mon programme source en autant de fichiers qu&rsquo;il y avait de fonctions (une petite centaine). Et de les comparer une-\u00e0-une (d&rsquo;abord avec <a title=\"http:\/\/fr.wikipedia.org\/wiki\/Diff\" href=\"http:\/\/fr.wikipedia.org\/wiki\/Diff\" target=\"_blank\"><code>diff<\/code><\/a> en console pour voir si\u00a0 quelque chose me sautait aux yeux, puis avec Meld pour les fonctions les plus longues).<\/p>\n<p style=\"padding-left: 60px; text-align: justify;\">Il existe un utilitaire nomm\u00e9 <a title=\"http:\/\/www.linux-france.org\/article\/man-fr\/man1\/csplit-1.html\" href=\"http:\/\/www.linux-france.org\/article\/man-fr\/man1\/csplit-1.html\" target=\"_blank\"><code>csplit<\/code><\/a> qui permet de d\u00e9couper en fichier en utilisant des expressions r\u00e9guli\u00e8res pour d\u00e9crire le contexte (le \u00ab\u00a0<code>c<\/code>\u00a0\u00bb de <code>csplit<\/code> signifie <em>context<\/em>, pas \u00ab\u00a0langage C\u00a0\u00bb !). Malheureusement, dans les blocs de code comment\u00e9s, il se trouvait des d\u00e9clarations de fonctions avort\u00e9es, et du code invalide. La description sous forme d&rsquo;expression r\u00e9guli\u00e8re semblait impossible.<\/p>\n<p style=\"text-align: justify;\">J&rsquo;ai commenc\u00e9 par demander \u00e0 <a title=\"http:\/\/ctags.sourceforge.net\/\" href=\"http:\/\/ctags.sourceforge.net\/\" target=\"_blank\"><code>ctags<\/code><\/a> d&rsquo;extraire la liste des fonctions pr\u00e9sentes, et de me la pr\u00e9senter de mani\u00e8re \u00ab\u00a0humainement lisible\u00a0\u00bb.<\/p>\n<p>Je prends en exemple le fichier <code><a title=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2014-10-20\/main.c\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2014-10-20\/main.c\" target=\"_blank\">main.c<\/a><\/code> se trouvant dans le r\u00e9pertoire <code>init\/<\/code> des sources du noyau Linux.<\/p>\n<pre>$ <strong>ctags -x -f - --c-kinds=f main.c<\/strong> \nboot_cpu_init    function    431 main.c           static void __init boot_cpu_init(void)\ndebug_kernel     function    195 main.c           static int __init debug_kernel(char *str)\ndo_basic_setup   function    777 main.c           static void __init do_basic_setup(void)\ndo_ctors         function    647 main.c           static void __init do_ctors(void)\ndo_early_param   function    389 main.c           static int __init do_early_param(char *param, char *val, const char *unused)\ndo_initcall_level function    746 main.c           static void __init do_initcall_level(int level)\ndo_initcalls     function    762 main.c           static void __init do_initcalls(void)\ndo_one_initcall  function    680 main.c           int __init_or_module do_one_initcall(initcall_t fn)\ndo_one_initcall_debug function    662 main.c           static int __init_or_module do_one_initcall_debug(initcall_t fn)\ndo_pre_smp_initcalls function    789 main.c           static void __init do_pre_smp_initcalls(void)\ninit_setup       function    291 main.c           static int __init init_setup(char *str)\nkernel_init      function    807 main.c           static int __ref kernel_init(void *unused)\nkernel_init_freeable function    848 main.c           static noinline void __init kernel_init_freeable(void)\nloglevel         function    210 main.c           static int __init loglevel(char *str)\nmark_rodata_ro   function     92 main.c           static inline void mark_rodata_ro(void) { }\nmm_init          function    454 main.c           static void __init mm_init(void)\nobsolete_checksetup function    158 main.c           static int __init obsolete_checksetup(char *line)\nparse_early_options function    407 main.c           void __init parse_early_options(char *cmdline)\nparse_early_param function    413 main.c           void __init parse_early_param(void)\nquiet_kernel     function    201 main.c           static int __init quiet_kernel(char *str)\nrdinit_setup     function    308 main.c           static int __init rdinit_setup(char *str)\nrepair_env_string function    230 main.c           static int __init repair_env_string(char *param, char *val, const char *unused)\nrest_init        function    360 main.c           static noinline void __init_refok rest_init(void)\nrun_init_process function    797 main.c           static int run_init_process(const char *init_filename)\nset_reset_devices function    144 main.c           static int __init set_reset_devices(char *str)\nsetup_command_line function    341 main.c           static void __init setup_command_line(char *command_line)\nsetup_nr_cpu_ids function    331 main.c           static inline void setup_nr_cpu_ids(void) { }\nsmp_init         function    323 main.c           static void __init smp_init(void)\nsmp_prepare_cpus function    332 main.c           static inline void smp_prepare_cpus(unsigned int maxcpus) { }\nsmp_setup_processor_id function    441 main.c           void __init __weak smp_setup_processor_id(void)\nstart_kernel     function    468 main.c           asmlinkage void __init start_kernel(void)\nthread_info_cache_init function    446 main.c           void __init __weak thread_info_cache_init(void)\nunknown_bootoption function    250 main.c           static int __init unknown_bootoption(char *param, char *val, const char *unused)<\/pre>\n<p style=\"text-align: justify;\">Nous voyons que chaque ligne correspond \u00e0 une fonction du fichier. Le premier champ est le nom de la fonction, il est suivi par le type d&rsquo;item (il s&rsquo;agit toujours de \u00ab\u00a0<code>function<\/code>\u00a0\u00bb car c&rsquo;est ce que j&rsquo;ai demand\u00e9 \u00e0 <code>ctags<\/code>). Le troisi\u00e8me champ est le num\u00e9ro de la premi\u00e8re ligne, le quatri\u00e8me le nom du fichier et enfin nous trouvons le prototype de la fonction.<\/p>\n<p style=\"text-align: justify;\">Je souhaite extraire chaque fonction s\u00e9par\u00e9ment, il me suffit donc de parcourir cette liste, et de cr\u00e9er un fichier \u00e0 chaque ligne. L&rsquo;impl\u00e9mentation de chaque fonction commence &#8211; dans le fichier original &#8211; \u00e0 la ligne dont le num\u00e9ro est indiqu\u00e9 en troisi\u00e8me colonne. Comme je ne sais pas o\u00f9 la fonction se termine, il me suffit de prendre tout le contenu jusqu&rsquo;\u00e0 la fonction suivante.<\/p>\n<p style=\"text-align: justify;\">Pour cela il faut ordonner la liste ci-dessus. Utilisons <a title=\"http:\/\/www.linux-france.org\/article\/man-fr\/man1\/sort-1.html\" href=\"http:\/\/www.linux-france.org\/article\/man-fr\/man1\/sort-1.html\" target=\"_blank\"><code>sort<\/code><\/a>.<\/p>\n<pre>$ <strong>ctags -x -f - --c-kinds=f main.c | sort -nk 3<\/strong>\nmark_rodata_ro   function     92 main.c           static inline void mark_rodata_ro(void) { }\nset_reset_devices function    144 main.c           static int __init set_reset_devices(char *str)\nobsolete_checksetup function    158 main.c           static int __init obsolete_checksetup(char *line)\ndebug_kernel     function    195 main.c           static int __init debug_kernel(char *str)\nquiet_kernel     function    201 main.c           static int __init quiet_kernel(char *str)\nloglevel         function    210 main.c           static int __init loglevel(char *str)\nrepair_env_string function    230 main.c           static int __init repair_env_string(char *param, char *val, const char *unused)\nunknown_bootoption function    250 main.c           static int __init unknown_bootoption(char *param, char *val, const char *unused)\ninit_setup       function    291 main.c           static int __init init_setup(char *str)\nrdinit_setup     function    308 main.c           static int __init rdinit_setup(char *str)\nsmp_init         function    323 main.c           static void __init smp_init(void)\nsetup_nr_cpu_ids function    331 main.c           static inline void setup_nr_cpu_ids(void) { }\nsmp_prepare_cpus function    332 main.c           static inline void smp_prepare_cpus(unsigned int maxcpus) { }\nsetup_command_line function    341 main.c           static void __init setup_command_line(char *command_line)\nrest_init        function    360 main.c           static noinline void __init_refok rest_init(void)\ndo_early_param   function    389 main.c           static int __init do_early_param(char *param, char *val, const char *unused)\nparse_early_options function    407 main.c           void __init parse_early_options(char *cmdline)\nparse_early_param function    413 main.c           void __init parse_early_param(void)\nboot_cpu_init    function    431 main.c           static void __init boot_cpu_init(void)\nsmp_setup_processor_id function    441 main.c           void __init __weak smp_setup_processor_id(void)\nthread_info_cache_init function    446 main.c           void __init __weak thread_info_cache_init(void)\nmm_init          function    454 main.c           static void __init mm_init(void)\nstart_kernel     function    468 main.c           asmlinkage void __init start_kernel(void)\ndo_ctors         function    647 main.c           static void __init do_ctors(void)\ndo_one_initcall_debug function    662 main.c           static int __init_or_module do_one_initcall_debug(initcall_t fn)\ndo_one_initcall  function    680 main.c           int __init_or_module do_one_initcall(initcall_t fn)\ndo_initcall_level function    746 main.c           static void __init do_initcall_level(int level)\ndo_initcalls     function    762 main.c           static void __init do_initcalls(void)\ndo_basic_setup   function    777 main.c           static void __init do_basic_setup(void)\ndo_pre_smp_initcalls function    789 main.c           static void __init do_pre_smp_initcalls(void)\nrun_init_process function    797 main.c           static int run_init_process(const char *init_filename)\nkernel_init      function    807 main.c           static int __ref kernel_init(void *unused)\nkernel_init_freeable function    848 main.c           static noinline void __init kernel_init_freeable(void)\n<\/pre>\n<p style=\"text-align: justify;\">Une simple boucle <code>while read<\/code> du shell nous donnera&nbsp;:<\/p>\n<pre>$ <strong>ctags -x -f - --c-kinds=f main.c | sort -nk 3 | while read name unused line rest; do echo \"${name}:  ${line}\"; done<\/strong>\nmark_rodata_ro:  92\nset_reset_devices:  144\nobsolete_checksetup:  158\ndebug_kernel:  195\nquiet_kernel:  201\nloglevel:  210\nrepair_env_string:  230\nunknown_bootoption:  250\ninit_setup:  291\nrdinit_setup:  308\nsmp_init:  323\nsetup_nr_cpu_ids:  331\nsmp_prepare_cpus:  332\nsetup_command_line:  341\nrest_init:  360\ndo_early_param:  389\nparse_early_options:  407\nparse_early_param:  413\nboot_cpu_init:  431\nsmp_setup_processor_id:  441\nthread_info_cache_init:  446\nmm_init:  454\nstart_kernel:  468\ndo_ctors:  647\ndo_one_initcall_debug:  662\ndo_one_initcall:  680\ndo_initcall_level:  746\ndo_initcalls:  762\ndo_basic_setup:  777\ndo_pre_smp_initcalls:  789\nrun_init_process:  797\nkernel_init:  807\nkernel_init_freeable:  848<\/pre>\n<p style=\"text-align: justify;\">Il nous faut maintenant extraire du fichier original la portion contenue entre le num\u00e9ro indiqu\u00e9 sur chaque ligne et celui de la ligne suivante.<\/p>\n<p style=\"text-align: justify;\">Une solution simple est d&rsquo;utiliser <a title=\"http:\/\/fr.wikipedia.org\/wiki\/Stream_Editor\" href=\"http:\/\/fr.wikipedia.org\/wiki\/Stream_Editor\" target=\"_blank\"><code>sed<\/code><\/a>. Une commande comme<\/p>\n<pre>$ <strong>sed -ne '123,456p' &lt; fic-entree &gt; fic-sortie<\/strong><\/pre>\n<p style=\"text-align: justify;\">copiera (commande <code>p<\/code> pour <em>print<\/em>) dans le fichier de sortie le contenu du fichier d&rsquo;entr\u00e9e depuis la ligne 123 jusqu&rsquo;\u00e0 la ligne 456. De m\u00eame<\/p>\n<pre>$ <strong>sed -ne '2014,$p' &lt;  fic-entree &gt; fic-sortie<\/strong><\/pre>\n<p style=\"text-align: justify;\">fera la copie depuis la ligne 2014 jusqu&rsquo;\u00e0 la fin du fichier. Attention \u00e0 bien mettre des quotes simples pour encadrer la commande afin que le shell n&rsquo;interpr\u00e8te pas le <code>$p<\/code> comme un nom de variable.<\/p>\n<p style=\"text-align: justify;\">Prenons les derni\u00e8res lignes affich\u00e9es ci-dessus. Lorsque nous traitons la ligne<\/p>\n<pre>kernel_init:  807<\/pre>\n<p>nous ne savons pas encore o\u00f9 la fonction <code>kernel_init<\/code> se terminera. Il nous faut donc m\u00e9moriser son nom et le num\u00e9ro 807, pour qu&rsquo;une fois lue la ligne<\/p>\n<pre>kernel_init_freeable:  848<\/pre>\n<p>nous puissions extraire la portion 807 &#8211; (848-1) du fichier original pour le stocker dans un nouveau fichier nomm\u00e9 <code>function-kernel_init<\/code> par exemple.<\/p>\n<p>Pour pouvoir traiter la derni\u00e8re fonction de la liste, il faut que notre boucle <code>while read<\/code> soit appel\u00e9e une fois de plus, aussi devons nous ajouter une ligne suppl\u00e9mentaire ainsi&nbsp;:<\/p>\n<pre>$ <strong><span style=\"text-decoration: underline;\">(<\/span>ctags -x -f - --c-kinds=f main.c | sort -nk 3<span style=\"text-decoration: underline;\"> ; echo)<\/span> | while read name unused line rest; do echo \"${name}:  ${line}\"; done<\/strong>\nmark_rodata_ro:  92\nset_reset_devices:  144\nobsolete_checksetup:  158\ndebug_kernel:  195\nquiet_kernel:  201\nloglevel:  210\nrepair_env_string:  230\nunknown_bootoption:  250\ninit_setup:  291\nrdinit_setup:  308\nsmp_init:  323\nsetup_nr_cpu_ids:  331\nsmp_prepare_cpus:  332\nsetup_command_line:  341\nrest_init:  360\ndo_early_param:  389\nparse_early_options:  407\nparse_early_param:  413\nboot_cpu_init:  431\nsmp_setup_processor_id:  441\nthread_info_cache_init:  446\nmm_init:  454\nstart_kernel:  468\ndo_ctors:  647\ndo_one_initcall_debug:  662\ndo_one_initcall:  680\ndo_initcall_level:  746\ndo_initcalls:  762\ndo_basic_setup:  777\ndo_pre_smp_initcalls:  789\nrun_init_process:  797\nkernel_init:  807\nkernel_init_freeable:  848\n:  \n<\/pre>\n<p style=\"text-align: justify;\">lors de la derni\u00e8re it\u00e9ration tous les champs sont vides, ce qui conduit \u00e0 la ligne contenant seulement le caract\u00e8re deux-points.<\/p>\n<p style=\"text-align: justify;\">Nous pouvons maintenant regrouper tout ceci dans un petit script qui prendra autant de fichiers d&rsquo;entr\u00e9e qu&rsquo;on le souhaite.<\/p>\n<pre><strong><a title=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2014-10-20\/extract-functions.sh\" href=\"http:\/\/www.blaess.fr\/christophe\/files\/article-2014-10-20\/extract-functions.sh\" target=\"_blank\">extract-functions.sh:<\/a><\/strong>\n#! \/bin\/sh\n\nif [ $# -eq 0 ]\nthen\n  echo \"usage: $0 C-file...\" &gt;&amp;2\n  exit 1\nfi\n\nfor file in \"$@\"\ndo\n  prev_line=\"\"\n  prev_name=\"\"\n  (ctags -x -f - --c-kinds=f \"${file}\" | \\\n   sort -n -k 3                        ;\n   echo)                               | \\\n  while read name unused line rest\n  do\n    if [ \"$prev_name\" != \"\" ] # avoid first line\n    then\n      if [ \"${name}\" != \"\" ]\n      then\n        # normal line\n        sed -ne \"${prev_line},$((line - 1))p\" &lt; \"${file}\" &gt; \"function-${prev_name}.c\"\n      else\n        # last line\n        sed -ne \"${prev_line},\\$p\" &lt; \"${file}\" &gt; \"function-${prev_name}.c\"\n      fi\n    fi\n    prev_line=\"${line}\"\n    prev_name=\"${name}\"\n  done\ndone<\/pre>\n<p style=\"text-align: justify;\">En voici un exemple d&rsquo;utilisation.<\/p>\n<pre>$ <strong>ls<\/strong>\nmain.c\n$ <strong>extract-functions.sh  main.c<\/strong> \n$ <strong>ls<\/strong>\nfunction-boot_cpu_init.c      function-do_one_initcall.c        function-mark_rodata_ro.c       function-repair_env_string.c   function-smp_prepare_cpus.c\nfunction-debug_kernel.c       function-do_one_initcall_debug.c  function-mm_init.c              function-rest_init.c           function-smp_setup_processor_id.c\nfunction-do_basic_setup.c     function-do_pre_smp_initcalls.c   function-obsolete_checksetup.c  function-run_init_process.c    function-start_kernel.c\nfunction-do_ctors.c           function-init_setup.c             function-parse_early_options.c  function-set_reset_devices.c   function-thread_info_cache_init.c\nfunction-do_early_param.c     function-kernel_init.c            function-parse_early_param.c    function-setup_command_line.c  function-unknown_bootoption.c\nfunction-do_initcall_level.c  function-kernel_init_freeable.c   function-quiet_kernel.c         function-setup_nr_cpu_ids.c    main.c\nfunction-do_initcalls.c       function-loglevel.c               function-rdinit_setup.c         function-smp_init.c\n$<\/pre>\n<p style=\"text-align: justify;\">En conclusion, ce petit script est loin d&rsquo;\u00eatre parfait, il n&rsquo;\u00e9vite pas les lignes blanches en fin de fichier par exemple, mais sa simplicit\u00e9 le rend facile \u00e0 modifier pour l&rsquo;adapter \u00e0 d&rsquo;autres situations o\u00f9 il pourra \u00eatre utile.<\/p>","protected":false},"excerpt":{"rendered":"<p>J&rsquo;ai eu besoin il y a quelques jours de comparer deux versions d&rsquo;un m&ecirc;me fichier C apr&egrave;s qu&rsquo;il a subi de nombreuses modifications. La structure du fichier et le contenu ayant beaucoup boug&eacute;, les outils habituels (&eacute;diteurs, diff, meld, etc.) ne pouvaient me venir en aide. J&rsquo;avais besoin d&rsquo;extraire chaque fonction du programme pour qu&rsquo;elle [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8,13],"tags":[],"class_list":["post-4205","post","type-post","status-publish","format-standard","hentry","category-linux-2","category-shell"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/4205","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/comments?post=4205"}],"version-history":[{"count":15,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/4205\/revisions"}],"predecessor-version":[{"id":4224,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/posts\/4205\/revisions\/4224"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=4205"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/categories?post=4205"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/tags?post=4205"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}