<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Enodev.fr / Christophe's log (Posts about Software)</title><link>https://www.enodev.fr/</link><description></description><atom:link href="https://www.enodev.fr/categories/cat_software.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><lastBuildDate>Wed, 18 Jun 2025 17:32:20 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>XZ backdoor lessons: reproducing target-isns release tarballs</title><link>https://www.enodev.fr/posts/xz-backdoor-lessons-reproducing-target-isns-release-tarballs.html</link><dc:creator>Christophe Vu-Brugier</dc:creator><description>&lt;p&gt;Andres Freund detected a backdoor in
&lt;a href="https://github.com/tukaani-project/xz"&gt;XZ&lt;/a&gt;,
a data compression library, and
&lt;a href="https://lwn.net/ml/oss-security/20240329155126.kjjfduxw2yrlxgzm@awork3.anarazel.de/"&gt;disclosed&lt;/a&gt;
it as
&lt;a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-3094"&gt;CVE-2024-3094&lt;/a&gt;
at the end of March 2024.&lt;/p&gt;
&lt;p&gt;Many good articles describe how the attackers implanted the backdoor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LWN: &lt;a href="https://lwn.net/Articles/967866/"&gt;Free software's not-so-eXZellent adventure&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;LWN: &lt;a href="https://lwn.net/Articles/967192/"&gt;How the XZ backdoor works&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Russ Cox: &lt;a href="https://research.swtch.com/xz-timeline"&gt;Timeline of the XZ open source attack&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The attackers prepared the backdoor for more than two years and
completed their work when they became maintainers. They published a
release tarball with a malicious build script not present in the
source code. The attackers uploaded the release tarball to GitHub.&lt;/p&gt;
&lt;p&gt;I maintain &lt;a href="https://github.com/open-iscsi/target-isns"&gt;target-isns&lt;/a&gt;, a
niche free software project packaged in several Linux
distributions. There is not much activity in the project because it is
mostly done. The last release of target-isns – version
&lt;a href="https://github.com/open-iscsi/target-isns/releases/tag/v0.6.8"&gt;v0.6.8&lt;/a&gt;
– happened in May 2020. I built the release tarball on my
machine and uploaded it to GitHub.&lt;/p&gt;
&lt;p&gt;I believe that one of the lessons of the XZ backdoor episode is
&lt;a href="https://en.wikipedia.org/wiki/Trust,_but_verify"&gt;"Trust, but verify"&lt;/a&gt;.
Applied to target-isns releases I published, this could mean:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How can users verify that the release tarball of target-isns matches the source code?&lt;/li&gt;
&lt;li&gt;How can we improve the release process of target-isns to ease that verification?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;How to verify a release tarball built with git-archive&lt;/h2&gt;
&lt;p&gt;target-isns is written in C and built with
&lt;a href="https://cmake.org"&gt;CMake&lt;/a&gt;. A custom target, named &lt;em&gt;dist&lt;/em&gt;, invokes
&lt;a href="https://git-scm.com/docs/git-archive"&gt;git-archive&lt;/a&gt; to generate a
tarball of the source code. Ironically, the tarball is compressed with XZ.&lt;/p&gt;
&lt;p&gt;To verify that the release tarball matches the source code, we must
compare the checksum of the published release tarball with one built
locally from the source code.&lt;/p&gt;
&lt;h3&gt;How to compute the checksum of the published release tarball&lt;/h3&gt;
&lt;p&gt;Let's download the release tarball for target-isns v0.6.8 from GitHub
and compute its SHA256 checksum:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-OL&lt;span class="w"&gt; &lt;/span&gt;--silent&lt;span class="w"&gt; &lt;/span&gt;https://github.com/open-iscsi/target-isns/releases/download/v0.6.8/target-isns-0.6.8.tar.xz
&lt;span class="gp"&gt;$ &lt;/span&gt;sha256sum&lt;span class="w"&gt; &lt;/span&gt;target-isns-0.6.8.tar.xz
&lt;span class="go"&gt;544de09a2073242b21f6859841a5016c79c4006a53435a79b5cfc6602a59db97  target-isns-0.6.8.tar.xz&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;How to reproduce the release tarball from the source code&lt;/h3&gt;
&lt;p&gt;We clone the repository with &lt;code&gt;git clone&lt;/code&gt; and checkout the tag we want
with &lt;code&gt;--branch v0.6.8&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;--branch&lt;span class="w"&gt; &lt;/span&gt;v0.6.8&lt;span class="w"&gt; &lt;/span&gt;https://github.com/open-iscsi/target-isns.git
&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;target-isns
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The command &lt;code&gt;git show v0.6.8&lt;/code&gt; reports that the tagger (it's me) signed
the tag with &lt;a href="https://www.gnupg.org"&gt;GnuPG&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://www.enodev.fr/stories/about-cvubrugier.html"&gt;about&lt;/a&gt; page mentions the
fingerprint of my public GnuPG key. We import that key &lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="https://www.enodev.fr/posts/xz-backdoor-lessons-reproducing-target-isns-release-tarballs.html#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;gpg&lt;span class="w"&gt; &lt;/span&gt;--keyserver&lt;span class="w"&gt; &lt;/span&gt;keyserver.ubuntu.com&lt;span class="w"&gt; &lt;/span&gt;--search-key&lt;span class="w"&gt; &lt;/span&gt;81D01C4E399FFEDEDBD93D7F08390B7DF2FC1876
&lt;span class="go"&gt;gpg: data source: http://185.125.188.27:11371&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1)&lt;/span&gt;     &lt;span class="go"&gt;Christophe Vu-Brugier &amp;lt;cvubrugier@example.org&amp;gt;&lt;/span&gt;
&lt;span class="go"&gt;          4096 bit RSA key 08390B7DF2FC1876, created: 2011-06-16&lt;/span&gt;
&lt;span class="go"&gt;Keys 1-1 of 1 for "81D01C4E399FFEDEDBD93D7F08390B7DF2FC1876".  Enter number(s), N)ext, or Q)uit &amp;gt; 1&lt;/span&gt;
&lt;span class="go"&gt;gpg: key 08390B7DF2FC1876: public key "Christophe Vu-Brugier &amp;lt;cvubrugier@example.org&amp;gt;" imported&lt;/span&gt;
&lt;span class="go"&gt;gpg: Total number processed: 1&lt;/span&gt;
&lt;span class="go"&gt;gpg:               imported: 1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After importing my public key, we verify the tag with &lt;code&gt;git tag --verify&lt;/code&gt; &lt;sup id="fnref2:1"&gt;&lt;a class="footnote-ref" href="https://www.enodev.fr/posts/xz-backdoor-lessons-reproducing-target-isns-release-tarballs.html#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;tag&lt;span class="w"&gt; &lt;/span&gt;--verify&lt;span class="w"&gt; &lt;/span&gt;v0.6.8
&lt;span class="go"&gt;object 52e4fd427b1aff902ef4e7bce9a9c2f6b358a5eb&lt;/span&gt;
&lt;span class="go"&gt;type commit&lt;/span&gt;
&lt;span class="go"&gt;tag v0.6.8&lt;/span&gt;
&lt;span class="go"&gt;tagger Christophe Vu-Brugier &amp;lt;cvubrugier@example.org&amp;gt; 1588947171 +0200&lt;/span&gt;

&lt;span class="go"&gt;target-isns v0.6.8&lt;/span&gt;
&lt;span class="go"&gt;gpg: Signature made Fri May  8 14:13:16 2020 UTC&lt;/span&gt;
&lt;span class="go"&gt;gpg:                using RSA key 81D01C4E399FFEDEDBD93D7F08390B7DF2FC1876&lt;/span&gt;
&lt;span class="go"&gt;gpg:                issuer "cvubrugier@example.org"&lt;/span&gt;
&lt;span class="go"&gt;gpg: Good signature from "Christophe Vu-Brugier &amp;lt;cvubrugier@example.org&amp;gt;" [unknown]&lt;/span&gt;
&lt;span class="go"&gt;gpg: WARNING: This key is not certified with a trusted signature!&lt;/span&gt;
&lt;span class="go"&gt;gpg:          There is no indication that the signature belongs to the owner.&lt;/span&gt;
&lt;span class="go"&gt;Primary key fingerprint: 81D0 1C4E 399F FEDE DBD9  3D7F 0839 0B7D F2FC 1876&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The tag signature is correct. We build a release tarball and compare
its checksum with the published release tarball.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;build
&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;build/
&lt;span class="gp"&gt;$ &lt;/span&gt;cmake&lt;span class="w"&gt; &lt;/span&gt;..
&lt;span class="go"&gt;-- The C compiler identification is GNU 12.2.0&lt;/span&gt;
&lt;span class="go"&gt;-- Detecting C compiler ABI info&lt;/span&gt;
&lt;span class="go"&gt;-- Detecting C compiler ABI info - done&lt;/span&gt;
&lt;span class="go"&gt;-- Check for working C compiler: /usr/bin/cc - skipped&lt;/span&gt;
&lt;span class="go"&gt;-- Detecting C compile features&lt;/span&gt;
&lt;span class="go"&gt;-- Detecting C compile features - done&lt;/span&gt;
&lt;span class="go"&gt;-- Configuring done&lt;/span&gt;
&lt;span class="go"&gt;-- Generating done&lt;/span&gt;
&lt;span class="go"&gt;-- Build files have been written to: /target-isns/build&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;dist
&lt;span class="go"&gt;Built target dist&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;sha256sum&lt;span class="w"&gt; &lt;/span&gt;target-isns-0.6.8.tar.xz
&lt;span class="go"&gt;544de09a2073242b21f6859841a5016c79c4006a53435a79b5cfc6602a59db97  target-isns-0.6.8.tar.xz&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The checksum (&lt;code&gt;544de09a&lt;/code&gt;) of the generated tarball matches the
checksum of the published tarball: we reproduce the release tarball.&lt;/p&gt;
&lt;p&gt;I reproduced the release tarball on Debian 12 (bookworm). Does that
work with other Linux distributions?&lt;/p&gt;
&lt;h2&gt;Reproducing the release tarball from other Linux distributions&lt;/h2&gt;
&lt;p&gt;I use &lt;a href="https://podman.io"&gt;Podman&lt;/a&gt; to build the release tarball in several
Linux containers.&lt;/p&gt;
&lt;table class="table table-striped"&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Linux distribution&lt;/th&gt;
&lt;th&gt;Release tarball reproduced&lt;/th&gt;
&lt;th&gt;Git version&lt;/th&gt;
&lt;th&gt;XZ version&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alpine 3.19.1&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.43.0&lt;/td&gt;
&lt;td&gt;5.4.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="table-danger"&gt;
&lt;td&gt;Arch Linux&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;2.44.0&lt;/td&gt;
&lt;td&gt;5.6.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debian 11 (bullseye)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.30.2&lt;/td&gt;
&lt;td&gt;5.2.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debian 12 (bookworm)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.39.2&lt;/td&gt;
&lt;td&gt;5.4.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fedora 39&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.44.0&lt;/td&gt;
&lt;td&gt;5.4.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu 22.04 (jammy)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.34.1&lt;/td&gt;
&lt;td&gt;5.2.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu 24.04 (noble, pre-release)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.43.0&lt;/td&gt;
&lt;td&gt;5.4.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rocky Linux 8&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.39.3&lt;/td&gt;
&lt;td&gt;5.2.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rocky Linux 9&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.39.3&lt;/td&gt;
&lt;td&gt;5.2.5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The release tarball is reproducible everywhere except on Arch
Linux. The output of &lt;code&gt;git archive&lt;/code&gt; is the same but XZ compresses the
tarball differently. A binary search with &lt;code&gt;git bisect&lt;/code&gt; finds the
commit that introduce the change:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tukaani-project/xz/commit/6daa4d0ea46a8441f21f609149f3633158bf4704"&gt;XZ: Use threaded mode by default (as if --threads=0 was used).&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;XZ (version 5.6.1) on Arch Linux compresses data with several threads
by default. With &lt;code&gt;--threads=1&lt;/code&gt;, we can force XZ to compress data with
just one thread. With that, we can reproduce the release tarball of
target-isns on Arch Linux.&lt;/p&gt;
&lt;p&gt;Usually, there are different ways for a compressor to represent the
original data. That's why a compressor output may differ when its algorithm
or parameters change. To reduce the risk of compressed tarballs
varying across platforms, target-isns releases could switch to a
compressor less likely to change such as
&lt;a href="https://www.gnu.org/software/gzip/"&gt;gzip&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Future work&lt;/h2&gt;
&lt;p&gt;While existing release tarballs of target-isns are reproducible, we
should improve the release process.&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;Document the release process.&lt;/dt&gt;
&lt;dd&gt;A document should describe how releases are made and how to
  reproduce them.&lt;/dd&gt;
&lt;dt&gt;Setup a continuous integration pipeline to generate the release tarball from a tag.&lt;/dt&gt;
&lt;dd&gt;A continuous integration job should automatically generate the
  release tarball from a tag. The job should run in a container (for
  example a &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; image). With that, users
  could reproduce the release tarballs by running the same container
  image on their machine.&lt;/dd&gt;
&lt;dt&gt;Sign the release tarball with GnuPG.&lt;/dt&gt;
&lt;dd&gt;The maintainer should download the tarball generated by the release
  pipeline, reproduce it, and test it. Then, they should publish the
  release tarball, the SHA256 checksum file, and sign these files with
  their private GnuPG key.&lt;/dd&gt;
&lt;/dl&gt;
&lt;div class="caption"&gt;
  &lt;img src="https://www.enodev.fr/images/araignee-crabe.jpg" class="img-fluid rounded-circle" alt="A crab spider (Misumena vatia) caught a bee."&gt;
  &lt;p&gt;A crab spider (&lt;em&gt;&lt;a href="https://en.wikipedia.org/wiki/Misumena_vatia"&gt;Misumena vatia&lt;/a&gt;&lt;/em&gt;) caught a bee.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;I removed my email from the command output. &lt;a class="footnote-backref" href="https://www.enodev.fr/posts/xz-backdoor-lessons-reproducing-target-isns-release-tarballs.html#fnref:1" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;a class="footnote-backref" href="https://www.enodev.fr/posts/xz-backdoor-lessons-reproducing-target-isns-release-tarballs.html#fnref2:1" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>Git</category><category>GitHub</category><category>GnuPG</category><category>release</category><category>security</category><category>XZ</category><guid>https://www.enodev.fr/posts/xz-backdoor-lessons-reproducing-target-isns-release-tarballs.html</guid><pubDate>Sat, 13 Apr 2024 09:00:00 GMT</pubDate></item><item><title>Fin du QIF à la Société Générale</title><link>https://www.enodev.fr/posts/fin-du-qif-a-la-societe-generale.html</link><dc:creator>Christophe Vu-Brugier</dc:creator><description>&lt;div class="alert alert-info" role="alert"&gt;
  &lt;strong&gt;Mise à jour :&lt;/strong&gt;
    le QIF est de retour à la Société Générale depuis
    février 2020. Mais cet article reste pertinent si vous préférez
    manipuler le relevé de vos opérations au format CSV.
&lt;/div&gt;

&lt;p&gt;J'utilise &lt;a href="https://gnucash.org/"&gt;GnuCash&lt;/a&gt; depuis une dizaine d'années
pour tenir mes comptes. J'ai l'habitude d'importer le relevé des
opérations au format
&lt;a href="http://www.respmech.com/mym2qifw/qif_new.htm"&gt;QIF&lt;/a&gt; dans GnuCash. Or
depuis la refonte du site Internet destiné aux particuliers, la
Société Générale ne propose plus l'export du relevé des opération au
format QIF. À la place, la banque au carré rouge et noir propose un
fichier CSV contenant les opérations réalisées au cours des six
derniers mois.&lt;/p&gt;
&lt;p&gt;Contactée, la Société Générale m'a informé &lt;em&gt;« ne pas
pouvoir changer le format proposé par le site »&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Je constate qu'à l'image des &lt;acronym title="Google, Apple, Facebook,
Amazon et Microsoft"&gt;GAFAM&lt;/acronym&gt;, la Société Générale cherche à
capter les données de ses clients pour les exploiter et néglige leur
partage avec ces mêmes clients qui en sont pourtant les
propriétaires. Ainsi, le site Internet de la Société Générale propose
d'importer des comptes &lt;em&gt;externes&lt;/em&gt;, c'est-à-dire tenus dans &lt;em&gt;d'autres&lt;/em&gt;
établissements bancaires. Mais quiconque veut rapatrier &lt;em&gt;ses données&lt;/em&gt;
doit se contenter d'un fichier CSV réduit à la portion congrue.&lt;/p&gt;
&lt;p&gt;Nous allons étudier les problèmes liés au fichier CSV fourni par la
Société Générale et développer ensemble &lt;del&gt;l'esprit d'équipe&lt;/del&gt; une
solution pour faciliter son import dans GnuCash.&lt;/p&gt;
&lt;h2&gt;Du CSV à la française&lt;/h2&gt;
&lt;p&gt;Après avoir téléchargé le fichier CSV, observons ses propriétés :&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;$&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;Export_17052019_16112019.csv
Export_17052019_16112019.csv:&lt;span class="w"&gt; &lt;/span&gt;ISO-8859&lt;span class="w"&gt; &lt;/span&gt;text,&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;CRLF&lt;span class="w"&gt; &lt;/span&gt;line&lt;span class="w"&gt; &lt;/span&gt;terminators
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Le fichier est encodé en &lt;a href="https://fr.wikipedia.org/wiki/ISO/CEI_8859"&gt;ISO
8859&lt;/a&gt; et les retours à la
ligne sont matérialisés par deux octets
&lt;a href="https://fr.wikipedia.org/wiki/Carriage_Return_Line_Feed"&gt;CR+LF&lt;/a&gt; comme
il est d'usage sous Windows.&lt;/p&gt;
&lt;p&gt;Que contiennent donc &lt;em&gt;nos&lt;/em&gt; données ?&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;$ head Export_17052019_16112019.csv
="0201900016400270";17/05/2019;16/11/2019;
date_comptabilisation;libellé_complet_operation;montant_operation;devise;
15/11/2019;CARTE X7527 15/11 METRO    ;-14,90;EUR;
13/11/2019;VIR RECU    8527975563S DE: Boulot SAS MOTIF: BOULOT OCTOBRE 2019 33 REF: BOULOT OCTOBRE 2019 33;2188,28;EUR;
11/11/2019;PRELEVEMENT EUROPEEN 7997325297 DE: DODO GESTION ID: FR65ZZZ233162 MOTIF: 081 LOYER DODO;-614,50;EUR;
10/11/2019;CARTE X7527 10/11 RESTO    ;-11,00;EUR;
09/11/2019;CARTE X7527 09/11 BRICO    ;-14,50;EUR;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nous avons affaire à la variante française du
&lt;a href="https://fr.wikipedia.org/wiki/Comma-separated_values"&gt;CSV&lt;/a&gt;
qui utilise le &lt;em&gt;point-virgule&lt;/em&gt; pour délimiter les colonnes et la
&lt;em&gt;virgule&lt;/em&gt; pour partager la partie entière et la partie décimale d'un
nombre.&lt;/p&gt;
&lt;p&gt;La première ligne débute par le numéro de compte. La notation
&lt;code&gt;="0123"&lt;/code&gt; est une astuce pour indiquer à Microsoft Excel de
&lt;a href="https://stackoverflow.com/questions/34595812/is-a-csv-with-equal-sign-valid"&gt;préserver le zéro&lt;/a&gt;
au début d'un nombre. Suivent les dates de début et de fin des
données exportées. Les dates sont exprimées au format français
&lt;em&gt;JJ/MM/AAAA&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;La deuxième ligne contient les intitulés de colonnes qui sont donc la
&lt;em&gt;date de comptabilisation&lt;/em&gt;, le &lt;em&gt;libellé de l'opération&lt;/em&gt;, son &lt;em&gt;montant&lt;/em&gt;
et la &lt;em&gt;devise&lt;/em&gt;. Nous remarquons que la ligne se termine par un
séparateur &lt;em&gt;point-virgule&lt;/em&gt; inutile.&lt;/p&gt;
&lt;h2&gt;Nettoyer le fichier CSV avec Pandas&lt;/h2&gt;
&lt;p&gt;Après avoir observé les données, nous ouvrons notre boîte à outils
d'apprenti
&lt;a href="https://fr.wikipedia.org/wiki/Science_des_donn%C3%A9es"&gt;Data Scientist&lt;/a&gt;.
Nous choisissons Python et Pandas.
&lt;a href="https://www.python.org/"&gt;Python&lt;/a&gt; est un langage de programmation
qu'on ne présente plus : il est même enseigné au Lycée.
&lt;a href="https://pandas.pydata.org/"&gt;Pandas&lt;/a&gt; est une bibliothèque Python
destinée à la fouille de données.
Python et Pandas, voilà une excellente compagnie pour le paresseux que
je suis.&lt;/p&gt;
&lt;h3&gt;Charger&lt;/h3&gt;
&lt;p&gt;La bibliothèque Pandas fournit la fonction
&lt;a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html"&gt;&lt;code&gt;read_csv()&lt;/code&gt;&lt;/a&gt;
avec de nombreux paramètres permettant de nettoyer le fichier CSV.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Export_17052019_16112019.csv'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;';'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;parse_dates&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dayfirst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;usecols&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;index_col&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'date'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'description'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'amount'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'currency'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                 &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'ISO-8859-1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Parmi les paramètres :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sep&lt;/code&gt; et &lt;code&gt;decimal&lt;/code&gt; adaptent le comportement de &lt;code&gt;read_csv()&lt;/code&gt; pour la
  variante française du CSV.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;header&lt;/code&gt; indique que la ligne des entêtes est la deuxième ligne (la
  première ligne ayant pour indice zéro).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dayfirst&lt;/code&gt; sélectionne le format de date européen où le jour
  apparaît avant le mois (les États-Unis fêtent Noël le 12/25).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;index_col&lt;/code&gt; précise que la première colonne – la date –
  sert d'index.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La fonction &lt;code&gt;read_csv()&lt;/code&gt; retourne un objet de type
&lt;a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html"&gt;&lt;code&gt;DataFrame&lt;/code&gt;&lt;/a&gt;.
Voici son contenu affiché par Python :&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&amp;gt;&amp;gt;&amp;gt; df
                                                  description   amount currency
date
2019-11-15                        CARTE X7527 15/11 METRO       -14.90      EUR
2019-11-13  VIR RECU    8527975563S DE: Boulot SAS MOTIF: ...  2188.28      EUR
2019-11-11  PRELEVEMENT EUROPEEN 7997325297 DE: DODO GESTI...  -614.50      EUR
2019-11-10                        CARTE X7527 10/11 RESTO       -11.00      EUR
2019-11-09                        CARTE X7527 09/11 BRICO       -14.50      EUR
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Filtrer&lt;/h3&gt;
&lt;p&gt;La version précédente du site Internet de la Société Générale
proposait d'exporter les transactions au format QIF &lt;em&gt;depuis le dernier
téléchargement&lt;/em&gt;. La nouvelle mouture propose un export au format CSV
des transactions des six derniers mois.&lt;/p&gt;
&lt;p&gt;Par conséquent, nous sommes désormais contraint de relever dans
GnuCash la date de la dernière transaction déjà importée afin de
filtrer les données CSV à importer et limiter les doublons.&lt;/p&gt;
&lt;p&gt;La première colonne – la date – ayant été choisie comme
index, l'opération se fait simplement avec un &lt;em&gt;slice&lt;/em&gt; :&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&amp;gt;&amp;gt;&amp;gt; df = df['2019-11-15':'2019-11-13']
&amp;gt;&amp;gt;&amp;gt; df
                                                  description   amount currency
date
2019-11-15                        CARTE X7527 15/11 METRO       -14.90      EUR
2019-11-13  VIR RECU    8527975563S DE: Boulot SAS MOTIF: ...  2188.28      EUR
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Distinguer les dépôts et les retraits&lt;/h3&gt;
&lt;p&gt;Le CSV exporté depuis le site Internet de la Société Générale comprend
une seule colonne &lt;em&gt;montant&lt;/em&gt;. Or GnuCash exige de distinguer &lt;em&gt;dépôt&lt;/em&gt; et
&lt;em&gt;retrait&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Nous devons donc ajouter deux colonnes au tableau et les remplir à
partir du montant.&lt;/p&gt;
&lt;p&gt;Pour une transaction donnée :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Si le montant est &lt;em&gt;positif&lt;/em&gt;, il s'agit d'un dépôt. Nous copions la
  valeur du montant dans la colonne dépôt et nous écrivons zéro dans
  la colonne débit.&lt;/li&gt;
&lt;li&gt;Si le montant est &lt;em&gt;négatif&lt;/em&gt;, il s'agit d'un retrait. Nous copions la
  valeur absolue du montant dans la colonne retrait et nous écrivons
  zéro dans la colonne dépôt.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avec Pandas, les colonnes &lt;em&gt;dépôt&lt;/em&gt; et &lt;em&gt;retrait&lt;/em&gt; peuvent être calculées
à partir du &lt;em&gt;montant&lt;/em&gt; de la manière suivante :&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'deposit'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'amount'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'withdraw'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'amount'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;En Python, le mot clé &lt;em&gt;lambda&lt;/em&gt; définit une fonction &lt;em&gt;anonyme&lt;/em&gt;. Dans la
première instruction, la colonne dépôt est remplie en appliquant au
montant la fonction qui pour &lt;em&gt;x&lt;/em&gt; retourne &lt;em&gt;x&lt;/em&gt; s'il est positif et 0
dans le cas contraire.&lt;/p&gt;
&lt;p&gt;Voici à quoi ressemble notre tableau suite à l'ajout des colonnes
dépôt et retrait :&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;                                                  description   amount currency  deposit  withdraw
date
2019-11-15                        CARTE X7527 15/11 METRO       -14.90      EUR     0.00      14.9
2019-11-13  VIR RECU    8527975563S DE: Boulot SAS MOTIF: ...  2188.28      EUR  2188.28       0.0
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Exporter à destination de Gnucash&lt;/h3&gt;
&lt;p&gt;La méthode
&lt;a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_csv.html#pandas.DataFrame.to_csv"&gt;&lt;code&gt;to_csv()&lt;/code&gt;&lt;/a&gt;
permet de sauvegarder un &lt;code&gt;DataFrame&lt;/code&gt; dans un
fichier au format CSV.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'gnucash.csv'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;';'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'description'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'deposit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'withdraw'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Seules sont exportées les colonnes utiles à GnuCash : date (implicite
car il s'agit de l'index), description, dépôt, et retrait.&lt;/p&gt;
&lt;p&gt;Notre fichier CSV &lt;em&gt;« nettoyé »&lt;/em&gt; est prêt à être
importé dans GnuCash.&lt;/p&gt;
&lt;h2&gt;Importer le fichier CSV final dans GnuCash&lt;/h2&gt;
&lt;p&gt;Dans l'interface graphique de GnuCash, nous cliquons sur : &lt;em&gt;fichier&lt;/em&gt;
→ &lt;em&gt;importer&lt;/em&gt; → &lt;em&gt;importer des transactions depuis un CSV&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Après avoir sélectionné notre fichier CSV, GnuCash nous présente la
fenêtre d'aperçu de l'import.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Aperçu de l'import dans GnuCash" class="rounded" src="https://www.enodev.fr/images/gnucash-import-preview.png"&gt;&lt;/p&gt;
&lt;p&gt;Nous appliquons les réglages suivants :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le point-virgule est défini comme séparateur de colonnes.&lt;/li&gt;
&lt;li&gt;La première ligne qui correspond à l'entête est ignorée.&lt;/li&gt;
&lt;li&gt;Le type des données de chaque colonne est précisé.&lt;/li&gt;
&lt;li&gt;Un compte cible est renseigné pour l'ensemble des transactions (champ &lt;em&gt;account&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le wiki de GnuCash décrit plus en détail comment
&lt;a href="https://wiki.gnucash.org/wiki/CSV_Import/Export#Importing_Transactions"&gt;importer des transactions au format CSV&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Pour aller plus loin&lt;/h2&gt;
&lt;p&gt;Le script &lt;a href="https://www.enodev.fr/csv-socgen/csv-socgen.py"&gt;csv-socgen.py&lt;/a&gt; reprend
l'ensemble du code présenté ici. Il ne s'agit que d'un premier jet.&lt;/p&gt;
&lt;p&gt;Le script mérite d'être amélioré pour remplir automatiquement le
compte destinataire et modifier la description si elle correspond à un
motif donné. Ainsi, le compte destinataire de la transaction ayant
pour description &lt;em&gt;METRO&lt;/em&gt; serait automatiquement renseigné en
&lt;em&gt;Dépenses:Transports publics:Métro&lt;/em&gt;. Remarquons que la banque affiche
une catégorie pour chaque transaction sur son site Internet mais que
cette information ne figure pas dans le fichier CSV. À nous de ruser.&lt;/p&gt;
&lt;p&gt;Un ancien slogan de la Société Générale clamait &lt;em&gt;« On est
là pour vous aider »&lt;/em&gt;. Cette ambition a disparu en 2019
avec un slogan devenu &lt;em&gt;« C'est vous
l'avenir »&lt;/em&gt;. Alors, ne comptez que sur vous-même pour tenir
vos comptes !&lt;/p&gt;</description><category>Banque</category><category>CSV</category><category>GnuCash</category><category>Pandas</category><category>Python</category><category>QIF</category><guid>https://www.enodev.fr/posts/fin-du-qif-a-la-societe-generale.html</guid><pubDate>Fri, 22 Nov 2019 18:00:00 GMT</pubDate></item><item><title>Rusticity: convert an integer to an enum</title><link>https://www.enodev.fr/posts/rusticity-convert-an-integer-to-an-enum.html</link><dc:creator>Christophe Vu-Brugier</dc:creator><description>&lt;p&gt;I am learning how to program in
&lt;a href="https://www.rust-lang.org/"&gt;Rust&lt;/a&gt;. Thinking in Rust is a delightful
experience and the more I practice Rust the more I feel how it
empowers developers to solve complex problems with confidence.&lt;/p&gt;
&lt;p&gt;However, I sometimes get frustrated on my way to rusticity. For
instance, when a programming task easily done in C or Python requires
more work in Rust. I experienced this when trying to convert an
integer to an enum. This blog post compares how this is usually done
in C and how to do it safely in Rust.&lt;/p&gt;
&lt;h2&gt;Converting an integer to an enum in C&lt;/h2&gt;
&lt;p&gt;In C, the enumeration constants have the type &lt;code&gt;int&lt;/code&gt;. Thus, an integer
value can be directly assigned to an enum.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;atomic_number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;HYDROGEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;HELIUM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;IRON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;atomic_number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IRON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Beware of Rust!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;While it is easy to assign an integer value to an enum, the C compiler
performs no bounds checking. Nothing prevents us from assigning an
impossible value to an &lt;code&gt;atomic_number&lt;/code&gt; enum.&lt;/p&gt;
&lt;h2&gt;Converting an integer to an enum in Rust, the naïve way&lt;/h2&gt;
&lt;p&gt;Let's write a similar program in Rust:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;AtomicNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;HYDROGEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;HELIUM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;IRON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;AtomicNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When we try to compile and run the program with &lt;code&gt;cargo run&lt;/code&gt;, the Rust
compiler reports a &lt;em&gt;mismatched types&lt;/em&gt; error:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="go"&gt;error[E0308]: mismatched types&lt;/span&gt;
&lt;span class="go"&gt; --&amp;gt; src/main.rs:9:33&lt;/span&gt;
&lt;span class="go"&gt;  |&lt;/span&gt;
&lt;span class="go"&gt;9 |     let element: AtomicNumber = 26;&lt;/span&gt;
&lt;span class="go"&gt;  |                  ------------   ^^ expected enum `AtomicNumber`, found integer&lt;/span&gt;
&lt;span class="go"&gt;  |                  |&lt;/span&gt;
&lt;span class="go"&gt;  |                  expected due to this&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The compiler error clearly indicates that &lt;code&gt;AtomicNumber&lt;/code&gt; and &lt;code&gt;integer&lt;/code&gt;
are two different types.&lt;/p&gt;
&lt;p&gt;To explicitly convert an integer to our &lt;code&gt;AtomicNumber&lt;/code&gt; enum, we can
write a conversion function that takes an unsigned 32-bits integer as
parameter and returns an &lt;code&gt;AtomicNumber&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;AtomicNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;HYDROGEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;HELIUM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;IRON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AtomicNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;from_u32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;AtomicNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AtomicNumber&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HYDROGEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AtomicNumber&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HELIUM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AtomicNumber&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;IRON&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;panic!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unknown value: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AtomicNumber&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from_u32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;from_u32()&lt;/code&gt; function is an &lt;em&gt;associated function&lt;/em&gt; of the
&lt;code&gt;AtomicNumber&lt;/code&gt; type because it is associated with that type,
and unlike a method does not take a first parameter named &lt;code&gt;self&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are several issues in &lt;code&gt;from_u32()&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When the given value does not match any variant in the enumeration,
  &lt;code&gt;panic!()&lt;/code&gt; terminates the program.&lt;/li&gt;
&lt;li&gt;Both the enumeration definition and the conversion function match
  enumeration variants with integers.
  This duplication is error-prone.&lt;/li&gt;
&lt;li&gt;The conversion function grows with the number of variants.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The periodic table contains more than 100 elements. Implementing a
conversion function for atomic numbers is boring and
error-prone. There is a better way.&lt;/p&gt;
&lt;h2&gt;Converting an integer to an enum in Rust with num-derive&lt;/h2&gt;
&lt;p&gt;A more elegant solution is to use the &lt;a href="https://docs.rs/num/0.4.0/num/trait.FromPrimitive.html"&gt;FromPrimitive
trait&lt;/a&gt;
from the &lt;a href="https://crates.io/crates/num"&gt;num&lt;/a&gt; crate coupled with syntax
extensions from the &lt;a href="https://crates.io/crates/num-derive"&gt;num-derive&lt;/a&gt;
crate.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;Cargo.toml&lt;/code&gt;, add dependencies for
&lt;a href="https://crates.io/crates/num"&gt;num&lt;/a&gt;,
&lt;a href="https://crates.io/crates/num-derive"&gt;num-derive&lt;/a&gt;, and
&lt;a href="https://crates.io/crates/num-traits"&gt;num-traits&lt;/a&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;[dependencies]&lt;/span&gt;
&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.4"&lt;/span&gt;
&lt;span class="n"&gt;num-derive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.4"&lt;/span&gt;
&lt;span class="n"&gt;num-traits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2"&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then, use the &lt;code&gt;#[derive]&lt;/code&gt; attribute:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;extern&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#[macro_use]&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num_derive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cp"&gt;#[derive(FromPrimitive)]&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;AtomicNumber&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;HYDROGEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;HELIUM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;IRON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;FromPrimitive&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;from_u32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AtomicNumber&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;IRON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Beware of Rust!"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unknown atomic number"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;#[derive(FromPrimitive)]&lt;/code&gt; attribute instructs the Rust compiler
to generate a basic implementation of the &lt;code&gt;FromPrimitive&lt;/code&gt; trait for
the &lt;code&gt;AtomicNumber&lt;/code&gt; enumeration. Unlike our handwritten &lt;code&gt;from_u32()&lt;/code&gt;
function, the conversion function generated by the Rust compiler
returns an &lt;a href="https://doc.rust-lang.org/std/option/"&gt;&lt;code&gt;Option&lt;/code&gt;&lt;/a&gt; which is
either &lt;code&gt;Some&lt;/code&gt; atomic number or &lt;code&gt;None&lt;/code&gt; if the given integer does not
match any known atomic number. This is much safer than calling
&lt;code&gt;panic!()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With the &lt;code&gt;#[derive(FromPrimitive)]&lt;/code&gt; attribute our Rust program is
nearly as concise as the same program written in C with the bonus
of being safer.&lt;/p&gt;
&lt;h2&gt;Harder, better, safer, rustier&lt;/h2&gt;
&lt;p&gt;While I had some hard time figuring how to convert an integer value to
an enum variant in Rust, I feel reassured by its type safety and pleased
by its ecosystem of &lt;a href="https://www.crates.io/"&gt;crates&lt;/a&gt;.&lt;/p&gt;
&lt;div class="caption"&gt;
  &lt;img src="https://www.enodev.fr/images/rust-pont-bir-hakeim.jpg" class="img-fluid rounded" alt="Rust on the pont de Bir-Hakeim"&gt;
  &lt;p&gt;Rust on the &lt;a href="https://en.wikipedia.org/wiki/Pont_de_Bir-Hakeim"&gt;pont de Bir-Hakeim&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><category>Programming</category><category>Rust</category><guid>https://www.enodev.fr/posts/rusticity-convert-an-integer-to-an-enum.html</guid><pubDate>Wed, 06 Sep 2017 18:00:00 GMT</pubDate></item><item><title>Le paquet targetcli-fb intègre Debian</title><link>https://www.enodev.fr/posts/targetcli-fb-integre-debian.html</link><dc:creator>Christophe Vu-Brugier</dc:creator><description>&lt;p&gt;Depuis quelques mois,
le &lt;a href="https://tracker.debian.org/pkg/targetcli-fb"&gt;paquet targetcli-fb&lt;/a&gt;
qui permet de configurer une target iSCSI avec LIO fait partie de
Debian Testing (la version en cours de développement
de &lt;a href="https://wiki.debian.org/fr/DebianStretch"&gt;Debian 9 "Stretch"&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;L'intégration de targetcli-fb vient combler un manque dans Debian. En
effet, aucun utilitaire n'est fourni dans l'actuelle Debian Stable
(Debian 8 "Jessie") pour configurer une target iSCSI avec LIO.&lt;/p&gt;
&lt;p&gt;Je participe à la maintenance du paquet targetcli-fb dans Debian avec
deux contributeurs plus expérimentés : Christian Seiler
et &lt;a href="https://www.researchut.com/"&gt;Ritesh Raj Sarraf&lt;/a&gt;. J'utilise
Debian depuis de nombreuses années et c'est un juste retour des choses
que de contribuer modestement (et librement) au projet.&lt;/p&gt;
&lt;p&gt;L'iSCSI sous Linux étant un sujet rarement traité sur le web
francophone, j'évoquerai comment s'en servir dans un futur article.&lt;/p&gt;</description><category>Debian</category><category>iSCSI</category><category>targetcli-fb</category><guid>https://www.enodev.fr/posts/targetcli-fb-integre-debian.html</guid><pubDate>Tue, 20 Dec 2016 19:00:00 GMT</pubDate></item><item><title>Mise à jour des fréquences de la TNT pour VLC</title><link>https://www.enodev.fr/posts/mise-a-jour-des-frequences-de-la-tnt-pour-vlc.html</link><dc:creator>Christophe Vu-Brugier</dc:creator><description>&lt;div class="alert alert-info" role="alert"&gt;
  &lt;strong&gt;Mise à jour, juin 2026 :&lt;/strong&gt;
  &lt;ul&gt;
  &lt;li&gt;La numérotation des chaînes de la TNT a changé depuis le &lt;a href="https://www.arcom.fr/presse/nouvelle-numerotation-des-chaines-compter-du-6-juin-2025-larcom-accompagne-les-telespectateurs"&gt;6 juin 2025&lt;/a&gt;. La liste de lecture a été modifiée en conséquence.&lt;/li&gt;
  &lt;li&gt;La recherche des fréquences avec la commande &lt;code&gt;w_scan&lt;/code&gt; fonctionne à nouveau. Il n'est plus nécessaire d'utiliser Kaffeine à cet effet.&lt;/li&gt;
  &lt;li&gt;La commande &lt;code&gt;w_scan&lt;/code&gt; est maintenant capable de générer une liste de lecture au format XSPF.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;

&lt;p&gt;Depuis le 5 avril 2016, la haute définition a été généralisée sur la
TNT. Il faut donc rechercher les fréquences des chaînes désormais en
haute définition. Sous Linux, on emploie la commande
&lt;a href="https://www.linuxtv.org/wiki/index.php/W_scan"&gt;&lt;code&gt;w_scan&lt;/code&gt;&lt;/a&gt;
à cet effet :&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;w_scan&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;FR&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;tnt.xspf
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La liste de lecture &lt;code&gt;xspf&lt;/code&gt; générée est un fichier au format XML. Il est nécessaire de l'éditer pour corriger l'ordre des chaînes de télévision, leur titre (champ &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;) et leur numéro (champ &lt;code&gt;&amp;lt;vlc:id&amp;gt;&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Ensuite, pour regarder la télévision avec VLC, il suffit de fournir la
liste de lecture en argument.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;vlc&lt;span class="w"&gt; &lt;/span&gt;tnt.xspf
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Si vous habitez en Île-de-France, voici une liste de lecture pour la TNT :
&lt;a href="https://www.enodev.fr/dl/tnt-paris.xspf"&gt;tnt-paris.xspf&lt;/a&gt;
(émetteur de la tour Eiffel).&lt;/p&gt;
&lt;div class="caption"&gt;
  &lt;img src="https://www.enodev.fr/images/emetteur-tour-eiffel.thumbnail.jpg" class="img-fluid rounded" alt="L'émetteur de la tour Eiffel"&gt;
  &lt;p&gt;Branchez une antenne plus grande pour mieux recevoir la TNT !&lt;br&gt;
  &lt;small&gt;L'émetteur de la Tour Eiffel&lt;/small&gt;&lt;/p&gt;
&lt;/div&gt;</description><category>Télévision</category><category>TNT</category><category>VLC</category><guid>https://www.enodev.fr/posts/mise-a-jour-des-frequences-de-la-tnt-pour-vlc.html</guid><pubDate>Mon, 23 May 2016 14:59:24 GMT</pubDate></item><item><title>Au début était la tortue Logo</title><link>https://www.enodev.fr/posts/au-debut-etait-la-tortue-logo.html</link><dc:creator>Christophe Vu-Brugier</dc:creator><description>&lt;p&gt;Quand j'étais à l'école primaire, notre classe disposait de deux
ordinosaures Thomson &lt;a href="https://fr.wikipedia.org/wiki/Thomson_MO5"&gt;MO5&lt;/a&gt;
et &lt;a href="https://fr.wikipedia.org/wiki/Thomson_TO7"&gt;TO7&lt;/a&gt;, reliques du plan
français
&lt;a href="https://fr.wikipedia.org/wiki/Plan_informatique_pour_tous"&gt;informatique pour tous&lt;/a&gt;. Ces
deux machines étaient déjà dépassées à l'époque et décoraient le fond
de notre classe.&lt;/p&gt;
&lt;p&gt;Un jour, l'institutrice dépoussiéra un des ordinateurs Thomson et le
démarra. Je me souviens que cet ordinateur austère, avec son crayon
optique, était particulièrement pénible à utiliser. Son utilisation
difficile avait de quoi entamer la curiosité d'un enfant, mais
l'ordinateur proposait néanmoins une application amusante : la tortue
Logo.&lt;/p&gt;
&lt;p&gt;C'est cette tortue Logo que je vais essayer d'animer dans cet article
en présentant un script écrit en Python capable d'afficher sa
trace. Le tout en moins de 100 lignes de code que je commenterai.&lt;/p&gt;
&lt;h2&gt;Principe de la tortue Logo&lt;/h2&gt;
&lt;p&gt;La tortue Logo est un robot qui se déplace à la surface de l'écran. Un
crayon est attaché à la tortue et trace son chemin quand il est
baissé. La tortue obéit aux commandes formulées par son guide comme
&lt;code&gt;AVANCE&lt;/code&gt;, &lt;code&gt;REPETE&lt;/code&gt; ou &lt;code&gt;TOURNEDROITE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Voici plusieurs polygones tracés par la tortue Logo, du triangle à
l'octogone :&lt;/p&gt;
&lt;div class="row"&gt;
&lt;div class="col-md-3"&gt;

&lt;object type="image/svg+xml" data="https://www.enodev.fr/tortue-logo/polygones.svg"&gt;
&lt;/object&gt;
&lt;/div&gt;
&lt;div class="col-md-9"&gt;
&lt;pre class="code literal-block"&gt;
REPETE 3 [ AVANCE 50 TOURNEDROITE 120 ]
REPETE 4 [ AVANCE 50 TOURNEDROITE 90 ]
REPETE 5 [ AVANCE 50 TOURNEDROITE 72 ]
REPETE 6 [ AVANCE 50 TOURNEDROITE 60 ]
REPETE 8 [ AVANCE 50 TOURNEDROITE 45 ]
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;Mise en oeuvre et contraintes&lt;/h2&gt;
&lt;p&gt;Je m'impose les contraintes suivantes pour le programme capable
d'afficher la trace d'une tortue Logo.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le programme doit peser moins de 100 lignes de code.&lt;/li&gt;
&lt;li&gt;Le programme est implémenté en Python. Comme chacun sait le python
  et la tortue sont des reptiles. Plus sérieusement, l'expressivité du
  langage Python et ses nombreux modules sont de précieux atouts pour
  respecter l'objectif de concision.&lt;/li&gt;
&lt;li&gt;Le code du programme est en français (même si les mots clefs sont en
  anglais). Je ne parlais pas anglais à l'école primaire. De plus, la
  tortue ne comprend que le français.&lt;/li&gt;
&lt;li&gt;Le programme enregistre la trace de la tortue dans une image adaptée
  à la publication sur le Web. Le format choisi est le format
  vectoriel SVG.&lt;/li&gt;
&lt;li&gt;Le programme est suffisamment élaboré pour "tracer mon logo en
  Logo". Mon logo est un space invader façon pixel art.&lt;/li&gt;
&lt;li&gt;Le programme n'est pas interactif. Il accepte en entrée un fichier
  contenant les commandes Logo et génère en sortie une image
  vectorielle SVG.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Ma tortue Logo en Python&lt;/h2&gt;
&lt;p&gt;Voici les 98 lignes de ma tortue Logo écrite en Python
(&lt;a href="https://www.enodev.fr/tortue-logo/tortue-logo.py"&gt;fichier source&lt;/a&gt;).
L'implémentation est grandement simplifiée en utilisant les
bibliothèques pyparsing et svgwrite.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="ch"&gt;#!/usr/bin/python3&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;namedtuple&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;math&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyparsing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OneOrMore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ParseResults&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replaceWith&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Word&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;svgwrite&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Drawing&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;svgwrite.path&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;av&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AV'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AVANCE'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParseAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replaceWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AV'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;bc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'BC'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'BAISSECRAYON'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParseAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replaceWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'BC'&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="n"&gt;lc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'LC'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'LEVECRAYON'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParseAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replaceWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'LC'&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="n"&gt;re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'RE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'RECULE'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParseAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replaceWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'RE'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;td&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'TD'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'TOURNEDROITE'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParseAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replaceWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'TD'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;tg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'TG'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'TOURNEGAUCHE'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParseAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replaceWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'TG'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;commande&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;av&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;bc&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;lc&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;td&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;tg&lt;/span&gt;
&lt;span class="n"&gt;repete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'REPETE'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'['&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suppress&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;OneOrMore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;commande&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;']'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suppress&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;logo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OneOrMore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repete&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;commande&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;LARGEUR_CANEVAS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;

&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;namedtuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Position'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x y'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tortue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LARGEUR_CANEVAS&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LARGEUR_CANEVAS&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# en degres : 0 =&amp;gt; haut, 90 =&amp;gt; droite&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crayon_bas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chemin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;'M&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stroke&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'black'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'white'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__radian&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cap&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;180.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;avance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nb_pas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__radian&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;nb_pas&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__radian&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;nb_pas&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crayon_bas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__svg_lineto&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nb_pas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;nb_pas&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tourne_droite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cap&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tourne_gauche&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tourne_droite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;baisse_crayon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crayon_bas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__svg_moveto&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;leve_crayon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crayon_bas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trace_commandes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;commandes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ParseResults&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;commandes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'AV'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'RE'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'TD'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tourne_droite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'TG'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tourne_gauche&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'BC'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baisse_crayon&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'LC'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leve_crayon&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'REPETE'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;nb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace_commandes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trace_programme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;programme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace_commandes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parseString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;programme&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trace_fichier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fichier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fichier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;contenu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace_programme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contenu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__svg_moveto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chemin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;'M&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__svg_lineto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chemin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;'L&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;enregistre_svg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fichier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;dessin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Drawing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fichier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'full'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'white'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;dessin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chemin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;dessin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'__main__'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tortue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tortue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;tortue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace_fichier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;tortue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enregistre_svg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Quelques commentaires&lt;/h2&gt;
&lt;h3&gt;Un peu de trigonométrie&lt;/h3&gt;
&lt;p&gt;Au départ, la tortue Logo est positionnée au centre de l'écran et
pointe vers le Nord. Depuis sa position initiale, elle se déplace pas
à pas et peut tourner sur sa droite ou sur sa gauche par degrés.&lt;/p&gt;
&lt;p&gt;Par exemple, la tortue Logo trace un triangle de 100 pas de côté quand
elle avance de 100 pas et tourne de 120 degrés sur sa droite à trois
reprises :&lt;/p&gt;
&lt;div class="row"&gt;
&lt;div class="col-md-3"&gt;
&lt;img src="https://www.enodev.fr/tortue-logo/triangle.png" class="img-fluid"&gt;
&lt;/div&gt;
&lt;div class="col-md-9"&gt;
&lt;pre class="code literal-block"&gt;
REPETE 3 [ AVANCE 100 TOURNEDROITE 120 ]
&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Pour calculer la position de la tortue après chaque déplacement, il
faut tenir compte :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;de la position initiale de la tortue ;&lt;/li&gt;
&lt;li&gt;de son orientation ;&lt;/li&gt;
&lt;li&gt;du nombre de pas qu'elle doit effectuer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Un soupçon de grammaire&lt;/h3&gt;
&lt;p&gt;La tortue Logo obéit à des commandes qui ressemblent au langage
naturel (&lt;code&gt;AVANCE&lt;/code&gt;, &lt;code&gt;TOURNEDROITE&lt;/code&gt;, &lt;code&gt;REPETE&lt;/code&gt;, etc.). Une partie de
notre programme est donc dévolue à l'analyse syntaxique (&lt;em&gt;parsing&lt;/em&gt;, en
anglais) des commandes Logo.&lt;/p&gt;
&lt;p&gt;La bibliothèque &lt;a href="https://github.com/pyparsing/pyparsing/"&gt;pyparsing&lt;/a&gt; permet
d'implémenter un analyseur syntaxique avec Python en décrivant une
&lt;a href="https://fr.wikipedia.org/wiki/Grammaire_formelle"&gt;grammaire formelle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Voici, par exemple, la grammaire formelle du mini langage Logo
interprétable par notre tortue.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nv"&gt;av&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;::=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"AVANCE"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;nombre&lt;/span&gt;
&lt;span class="nv"&gt;bc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;::=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"BAISSECRAYON"&lt;/span&gt;
&lt;span class="nv"&gt;lc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;::=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"LEVECRAYON"&lt;/span&gt;
&lt;span class="nv"&gt;re&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;::=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"RECULE"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;nombre&lt;/span&gt;
&lt;span class="nv"&gt;td&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;::=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"TOURNEDROITE"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;nombre&lt;/span&gt;
&lt;span class="nv"&gt;tg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;::=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"TOURNEGAUCHE"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;nombre&lt;/span&gt;
&lt;span class="nv"&gt;commande&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;::=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;av&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;lc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;re&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;td&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;tg&lt;/span&gt;
&lt;span class="nv"&gt;repete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;::=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"REPETE"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;nombre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"["&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;UnOuPlus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;commande&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"]"&lt;/span&gt;
&lt;span class="nv"&gt;logo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;::=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;UnOuPlus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;repete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;commande&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;La transcription en Python avec pyparsing se fait presque
directement. Pour chaque commande, on définit aussi un raccourci
(e.g. &lt;code&gt;AV&lt;/code&gt; pour &lt;code&gt;AVANCE&lt;/code&gt;) :&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;av&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AV'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AVANCE'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParseAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replaceWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AV'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;bc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'BC'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Keyword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'BAISSECRAYON'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParseAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replaceWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'BC'&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;commande&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;av&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;bc&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;lc&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;td&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;tg&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Une fois que pyparsing a digéré les commandes Logo, la fonction
&lt;code&gt;trace_commandes()&lt;/code&gt; identifie chaque commande et ses éventuels
paramètres et les transmet à la fonction associée :&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trace_commandes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;commandes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ParseResults&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;commandes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'AV'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'BC'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baisse_crayon&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'REPETE'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;nb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace_commandes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;À noter que la fonction &lt;code&gt;trace_commandes()&lt;/code&gt; utilise la récursivité
pour gérer la commande &lt;code&gt;REPETE&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Un chemin vers SVG&lt;/h3&gt;
&lt;p&gt;La trace de la tortue Logo est dessinée dans une image SVG avec la
bibliothèque &lt;a href="https://pypi.python.org/pypi/svgwrite"&gt;svgwrite&lt;/a&gt;. Le
programme n'utilise qu'un seul élément de SVG, le
&lt;a href="https://developer.mozilla.org/fr/docs/Web/SVG/Tutorial/Paths"&gt;path&lt;/a&gt;
qui est parfaitement adapté à notre usage.&lt;/p&gt;
&lt;p&gt;Un path SVG comprend un attribut &lt;code&gt;d&lt;/code&gt; (pour &lt;em&gt;data&lt;/em&gt;) qui est une liste
de commandes &lt;code&gt;L&lt;/code&gt; (pour &lt;em&gt;lineto&lt;/em&gt;) ou &lt;code&gt;M&lt;/code&gt; (pour &lt;em&gt;moveto&lt;/em&gt;) suivies des
coordonnées cartésiennes. La première commande d'un path SVG doit être
une commande &lt;em&gt;moveto&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Par exemple, pour tracer un angle droit, il faut donner les
coordonnées de trois points et tracer deux lignes entre ces trois
points :&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nt"&gt;&amp;lt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;d=&lt;/span&gt;&lt;span class="s"&gt;"M10,10  L10,100  L100,100"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src="https://www.enodev.fr/tortue-logo/angle.png" class="img-fluid"&gt;&lt;/p&gt;
&lt;p&gt;Dans le cas de notre tortue Logo, il suffit d'un path SVG pour
matérialiser sa trace.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Quand la tortue reçoit la commande &lt;code&gt;BAISSECRAYON&lt;/code&gt;, on ajoute une
  commande &lt;em&gt;moveto&lt;/em&gt; pour débuter sa trace.&lt;/li&gt;
&lt;li&gt;À chaque déplacement de la tortue, on ajoute une commande &lt;em&gt;lineto&lt;/em&gt;
  au path SVG si le crayon est baissé.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Pixel art vectoriel&lt;/h2&gt;
&lt;p&gt;Pour conclure, j'ai demandé à la tortue Logo de tracer le space
invader qui me sert d'avatar
(&lt;a href="https://www.enodev.fr/tortue-logo/space-invader.logo"&gt;fichier source&lt;/a&gt;).&lt;/p&gt;
&lt;object type="image/svg+xml" data="https://www.enodev.fr/tortue-logo/space-invader.svg" width="280" height="200"&gt;
&lt;/object&gt;

&lt;p&gt;Enfin, la vidéo suivante montre la tortue Logo filant à toute allure telle un lièvre.&lt;/p&gt;
&lt;video id="space-invader-video" controls autoplay="true" loop&gt;
  &lt;source src="https://www.enodev.fr/tortue-logo/space-invader.mp4" type="video/mp4"&gt;
&lt;/source&gt;&lt;/video&gt;

&lt;p&gt;Tortue Logo : &lt;code&gt;LEVECRAYON&lt;/code&gt;.&lt;/p&gt;</description><category>Logo</category><category>Python</category><category>SVG</category><category>Tortue</category><guid>https://www.enodev.fr/posts/au-debut-etait-la-tortue-logo.html</guid><pubDate>Sun, 20 Mar 2016 15:58:10 GMT</pubDate></item><item><title>Given enough compilers, all bugs are shallow</title><link>https://www.enodev.fr/posts/given-enough-compilers-all-bugs-are-shallow.html</link><dc:creator>Christophe Vu-Brugier</dc:creator><description>&lt;p&gt;This post title is a poorly paraphrased version of
&lt;a href="https://en.wikipedia.org/wiki/Linus's_Law"&gt;Linus' law&lt;/a&gt; &lt;em&gt;"given enough
eyeballs, all bugs are shallow"&lt;/em&gt;. What I want to illustrate is that
using more than one compiler allows to notice more bugs.&lt;/p&gt;
&lt;p&gt;I compiled some C++ code at work with Clang instead of GCC and it
spotted the following issue with &lt;code&gt;sleep()&lt;/code&gt; that GCC ignored:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As noticed by Clang, the argument of sleep is an &lt;code&gt;unsigned int&lt;/code&gt;, so
&lt;code&gt;sleep(0.5)&lt;/code&gt; is converted to &lt;code&gt;sleep(0)&lt;/code&gt;. This is what happens when
Pythonistas dare write some C code (Python's &lt;code&gt;time.sleep()&lt;/code&gt; accepts a
floating point number of seconds).&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;clang-3.8&lt;span class="w"&gt; &lt;/span&gt;-Wall&lt;span class="w"&gt; &lt;/span&gt;-Werror&lt;span class="w"&gt; &lt;/span&gt;test-sleep.c&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;test-sleep
&lt;span class="go"&gt;test-sleep.c:5:8: error: implicit conversion from 'double' to 'unsigned int' changes value from 0.5 to 0 [-Werror,-Wliteral-conversion]&lt;/span&gt;
&lt;span class="go"&gt;        sleep(0.5);&lt;/span&gt;
&lt;span class="go"&gt;        ~~~~~ ^~~&lt;/span&gt;
&lt;span class="go"&gt;1 error generated.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is not to say that Clang is better than GCC: both are great
compilers. But the effort of compiling a code base with GCC and Clang
is worth it because it catches more bugs.&lt;/p&gt;</description><category>C</category><category>Clang</category><category>GCC</category><category>Programming</category><guid>https://www.enodev.fr/posts/given-enough-compilers-all-bugs-are-shallow.html</guid><pubDate>Sun, 31 Jan 2016 17:00:00 GMT</pubDate></item><item><title>Emulate a slow block device with dm-delay</title><link>https://www.enodev.fr/posts/emulate-a-slow-block-device-with-dm-delay.html</link><dc:creator>Christophe Vu-Brugier</dc:creator><description>&lt;p&gt;When debugging a problem caused by high I/O latency on Linux, it may
be interesting to emulate a slow or congested block device. The device
mapper driver which manages logical volumes on Linux has a solution
for that: the &lt;em&gt;dm-delay&lt;/em&gt; target.&lt;/p&gt;
&lt;p&gt;In this article, we will use the &lt;em&gt;dm-delay&lt;/em&gt; target to delay reads and
writes to a block device. We will first create a ramdisk which is an
extremely fast block device. Then, we will stack the &lt;em&gt;dm-delay&lt;/em&gt; target
on top of it and measure the I/O latency it introduces.&lt;/p&gt;
&lt;h2&gt;Creating a ramdisk&lt;/h2&gt;
&lt;p&gt;A ramdisk is a RAM backed disk. Since data written to RAM do not
persist without power, do not store real data on a ramdisk. Compared
to a hard disk drive, a ramdisk is much smaller in size: its size
cannot exceed the computer RAM size. But a ramdisk is much faster than
a hard disk drive.&lt;/p&gt;
&lt;p&gt;On Linux, loading the
&lt;a href="https://elixir.bootlin.com/linux/latest/source/drivers/block/brd.c"&gt;brd&lt;/a&gt;
kernel module creates a set of ramdisks. Passing arguments to the
&lt;code&gt;modprobe&lt;/code&gt; command configures the number of ramdisks and their size:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rd_nr&lt;/code&gt; sets the maximum number of ramdisks.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rd_size&lt;/code&gt; sets the size of each ramdisk in &lt;abbr title="kibibytes"&gt;KiB&lt;/abbr&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The command below creates a 1 GB ramdisk:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;modprobe&lt;span class="w"&gt; &lt;/span&gt;brd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rd_nr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rd_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1048576&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;/dev/ram0
&lt;span class="go"&gt;brw-rw---- 1 root disk 1, 0 Aug 24 20:00 /dev/ram0&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;blockdev&lt;span class="w"&gt; &lt;/span&gt;--getsize&lt;span class="w"&gt; &lt;/span&gt;/dev/ram0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Display the size in 512-byte sectors&lt;/span&gt;
&lt;span class="go"&gt;2097152&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Creating a delayed target with dm-delay&lt;/h2&gt;
&lt;p&gt;The
&lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/delay.html"&gt;kernel documentation&lt;/a&gt;
explains how to configure a delayed target with &lt;code&gt;dmsetup&lt;/code&gt;. For
instance, you can use a script like this one to stack a delayed block
device on top of a given block device:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="c1"&gt;# Create a block device that delays reads for 500 ms&lt;/span&gt;
&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;blockdev&lt;span class="w"&gt; &lt;/span&gt;--getsize&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Size in 512-bytes sectors&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 &lt;/span&gt;&lt;span class="nv"&gt;$size&lt;/span&gt;&lt;span class="s2"&gt; delay &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt; 0 500"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dmsetup&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;delayed
&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Checking the latency of dm-delay&lt;/h2&gt;
&lt;p&gt;Let's check the latency introduced by &lt;em&gt;dm-delay&lt;/em&gt;. We use
&lt;a href="https://github.com/axboe/fio"&gt;fio&lt;/a&gt; to compare the latency of the
ramdisk (&lt;code&gt;/dev/ram0&lt;/code&gt;) with the latency of the delayed device
(&lt;code&gt;/dev/dm-0&lt;/code&gt;). The job file for fio that describes the I/O workload is
as follows:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;[random]&lt;/span&gt;
&lt;span class="c1"&gt;# Perform 4K random reads for 10 seconds using direct I/Os&lt;/span&gt;
&lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/dev/dm-0&lt;/span&gt;
&lt;span class="na"&gt;readwrite&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;randread&lt;/span&gt;
&lt;span class="na"&gt;blocksize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;4k&lt;/span&gt;
&lt;span class="na"&gt;ioengine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sync&lt;/span&gt;
&lt;span class="na"&gt;direct&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;time_based&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At the end of the run, fio displays a bunch of statistics. One of them
is the &lt;em&gt;completion latency&lt;/em&gt; (denoted as &lt;code&gt;clat&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ramdisk: 1.33 µs&lt;/li&gt;
&lt;li&gt;delayed block device: 499735.14 µs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The latency of the ramdisk is a few microseconds whereas the latency
of the delayed block device backed by the ramdisk is close to the
500 ms delay we requested.&lt;/p&gt;
&lt;p&gt;A similar experiment for writes shows that &lt;code&gt;dm-delay&lt;/code&gt; also delays
writes to the device. To delay writes with &lt;code&gt;dm-delay&lt;/code&gt;, give a second
set of parameters to &lt;code&gt;dmsetup&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="c1"&gt;# Create a block device that delays reads for 500 ms and writes for 300 ms&lt;/span&gt;
&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;blockdev&lt;span class="w"&gt; &lt;/span&gt;--getsize&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Size in 512-bytes sectors&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 &lt;/span&gt;&lt;span class="nv"&gt;$size&lt;/span&gt;&lt;span class="s2"&gt; delay &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt; 0 500 &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt; 0 300"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dmsetup&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;delayed
&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Suspending I/Os&lt;/h2&gt;
&lt;p&gt;The device mapper can also be requested to suspend and resume I/Os.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;dmsetup&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;suspend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/dm-0
&lt;span class="gp"&gt;$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;dmsetup&lt;span class="w"&gt; &lt;/span&gt;resume&lt;span class="w"&gt;  &lt;/span&gt;/dev/dm-0
&lt;/pre&gt;&lt;/div&gt;</description><category>device mapper</category><category>Linux</category><category>RAM</category><category>storage</category><category>testing</category><guid>https://www.enodev.fr/posts/emulate-a-slow-block-device-with-dm-delay.html</guid><pubDate>Tue, 25 Aug 2015 18:00:00 GMT</pubDate></item><item><title>Send F10 to GRUB with Minicom</title><link>https://www.enodev.fr/posts/send-f10-to-grub-with-minicom.html</link><dc:creator>Christophe Vu-Brugier</dc:creator><description>&lt;p&gt;I recently had to modify the kernel command-line of a machine that I
accessed with a serial console. GRUB displayed the following message
explaining that I had to &lt;em&gt;"press Ctrl-x or F10 to boot"&lt;/em&gt;.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="nv"&gt;Minimum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Emacs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;screen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;editing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;supported&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TAB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;lists&lt;/span&gt;
&lt;span class="nv"&gt;completions&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Press&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Ctrl&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;F10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;boot&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Ctrl&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;F2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;
&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ESC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;discard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;edits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;GRUB&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;menu&lt;/span&gt;.
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Unfortunately, pressing F10 in minicom was equivalent to hitting ESC:
my changes were discarded and I was sent back to the GRUB menu.&lt;/p&gt;
&lt;p&gt;It turns out that the following keystroke sequences allow to send F10
with minicom: ESC+O+Y or ESC+0.&lt;/p&gt;</description><category>GRUB</category><category>Minicom</category><category>serial console</category><guid>https://www.enodev.fr/posts/send-f10-to-grub-with-minicom.html</guid><pubDate>Fri, 08 May 2015 18:00:00 GMT</pubDate></item><item><title>Send desktop notifications with cron</title><link>https://www.enodev.fr/posts/send-desktop-notifications-with-cron.html</link><dc:creator>Christophe Vu-Brugier</dc:creator><description>&lt;p&gt;At work, I tend to check my mailbox too often. That's unnecessary. As
an experiment, I have decided to check it once every hour. Since I
don't want to spend "brain cycles" remembering when I should check my
mailbox, I have added a cron task to display a notification every
hour.&lt;/p&gt;
&lt;p&gt;Desktop notifications on Linux can be displayed with a command-line
utility named &lt;code&gt;notify-send&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;notify-send&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hi &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"it's time to check your email."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--icon&lt;span class="o"&gt;=&lt;/span&gt;dialog-information
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The notification is displayed by the
&lt;a href="https://wiki.archlinux.org/index.php/Desktop_notifications#Notification_servers"&gt;notification server&lt;/a&gt;
which is either part of your desktop environment or standalone. I
personally use &lt;a href="https://dunst-project.org/"&gt;dunst&lt;/a&gt; because my window
manager, &lt;a href="https://i3wm.org"&gt;i3&lt;/a&gt;, has no built-in notification server.&lt;/p&gt;
&lt;p&gt;However, when &lt;code&gt;notify-send&lt;/code&gt; is invoked by cron, nothing is
displayed. The script invoked by cron cannot communicate with the
desktop environment because the &lt;code&gt;DBUS_SESSION_BUS_ADDRESS&lt;/code&gt; variable is
not set.&lt;/p&gt;
&lt;p&gt;As a consequence, you have to retrieve the value of the
&lt;code&gt;DBUS_SESSION_BUS_ADDRESS&lt;/code&gt; for your session. You can do that by
parsing the &lt;code&gt;/proc/$PID/environ&lt;/code&gt; pseudo file of your window manager
(&lt;a href="https://i3wm.org"&gt;i3&lt;/a&gt; in my case):&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;DBUS_SESSION_BUS_ADDRESS&lt;span class="w"&gt; &lt;/span&gt;/proc/&lt;span class="k"&gt;$(&lt;/span&gt;pidof&lt;span class="w"&gt; &lt;/span&gt;i3&lt;span class="k"&gt;)&lt;/span&gt;/environ&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cut&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f2-
&lt;span class="go"&gt;unix:abstract=/tmp/dbus-ajRnZ5v7g9,guid=f8761603e1845a282e104e1a5515bcec&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I wrote the following shell script named &lt;code&gt;remind-email&lt;/code&gt; to grab the
bus address of the D-Bus session and display a desktop notification:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;DBUS_SESSION_BUS_ADDRESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;DBUS_SESSION_BUS_ADDRESS&lt;span class="w"&gt; &lt;/span&gt;/proc/&lt;span class="k"&gt;$(&lt;/span&gt;pidof&lt;span class="w"&gt; &lt;/span&gt;i3&lt;span class="k"&gt;)&lt;/span&gt;/environ&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cut&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f2-&lt;span class="k"&gt;)&lt;/span&gt;
notify-send&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hi &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOGNAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"it's time to check your email."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--icon&lt;span class="o"&gt;=&lt;/span&gt;dialog-information
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The script is invoked every hour by the following cron entry (use
&lt;code&gt;crontab -e&lt;/code&gt; to edit your cron tables):&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="c1"&gt;remind-email&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;</description><category>cron</category><category>D-Bus</category><category>notify-send</category><guid>https://www.enodev.fr/posts/send-desktop-notifications-with-cron.html</guid><pubDate>Fri, 27 Mar 2015 18:00:00 GMT</pubDate></item></channel></rss>