.mobohttps://dotmobo.github.io/2022-12-04T00:00:00+01:00Blog d'un Pythoniste DjangonauteDomain Driven Design en Rust2022-12-04T00:00:00+01:002022-12-04T00:00:00+01:00Morgantag:dotmobo.github.io,2022-12-04:/ddd-rust.html<p class="first last">Domain Driven Design en Rust</p>
<img alt="Rust" class="align-right" src="./images/rust.png" />
<div class="section" id="qu-est-ce-que-le-domain-driven-design">
<h2>1) Qu'est-ce que le Domain Driven Design ?</h2>
<p>Le Domain Driven Design (DDD) est un paradigme de développement de logiciels qui met l'accent sur la compréhension
approfondie du domaine dans lequel ton application sera utilisée.</p>
<p>En utilisant le DDD, tu peux donc imaginer des modèles de données et des algorithmes qui reflètent précisément les
concepts et les relations qui existent dans le domaine d'application. Cela te permet de créer des logiciels plus clairs et plus
faciles à comprendre. Le DDD est donc un outil très utile pour les développeurs de logiciels, en particulier pour
ceux qui travaillent sur des projets complexes ou qui doivent collaborer avec des domaines experts.</p>
</div>
<div class="section" id="comment-utiliser-le-ddd-dans-un-projet-en-rust">
<h2>1) Comment utiliser le DDD dans un projet en Rust ?</h2>
<p>Pour utiliser le DDD dans ton projet en <a class="reference external" href="https://rust-lang.org/">Rust</a>, tu dois d'abord comprendre les concepts et les relations qui existent
dans le domaine d'application. Cette phase d'analyse et de compréhension est la plus importante.
Cela peut t'aider à résoudre les problèmes de manière plus efficace pour maintenir et faire évoluer ton
application.</p>
<p>Ensuite, tu peux utiliser des structures en Rust pour représenter les concepts du domaine, ainsi que des fonctions
pour manipuler ces structures. Par exemple, si tu développes une application de gestion de compte bancaire, tu peux
créer des structures pour représenter les comptes bancaires, les transactions et les clients, ainsi que des fonctions
pour effectuer des opérations sur ces structures (depôt, retrait, consultation du solde, etc.).</p>
</div>
<div class="section" id="un-exemple-concret-de-ddd-en-rust-une-application-de-gestion-de-compte-bancaire">
<h2>3) Un exemple concret de DDD en Rust : une application de gestion de compte bancaire.</h2>
<p>Voici un exemple de code en Rust qui utilise le DDD pour gérer un compte bancaire :</p>
<div class="highlight"><pre><span></span><span class="k">use</span><span class="w"> </span><span class="n">chrono</span>::<span class="p">{</span><span class="n">DateTime</span><span class="p">,</span><span class="w"> </span><span class="n">Utc</span><span class="p">};</span><span class="w"></span>
<span class="k">struct</span> <span class="nc">Account</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">balance</span>: <span class="kt">u64</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">transactions</span>: <span class="nb">Vec</span><span class="o"><</span><span class="n">Transaction</span><span class="o">></span><span class="p">,</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="k">struct</span> <span class="nc">Transaction</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">amount</span>: <span class="kt">u64</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">date</span>: <span class="nc">DateTime</span><span class="o"><</span><span class="n">Utc</span><span class="o">></span><span class="p">,</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="k">struct</span> <span class="nc">Client</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">name</span>: <span class="nb">String</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">accounts</span>: <span class="nb">Vec</span><span class="o"><</span><span class="n">Account</span><span class="o">></span><span class="p">,</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="k">impl</span><span class="w"> </span><span class="n">Account</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">fn</span> <span class="nf">deposit</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">amount</span>: <span class="kt">u64</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">balance</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">amount</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">transactions</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">Transaction</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">amount</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">date</span>: <span class="nc">Utc</span>::<span class="n">now</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">fn</span> <span class="nf">withdraw</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">amount</span>: <span class="kt">u64</span><span class="p">)</span><span class="w"> </span>-> <span class="nb">Result</span><span class="o"><</span><span class="p">(),</span><span class="w"> </span><span class="o">&'</span><span class="nb">static</span><span class="w"> </span><span class="kt">str</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">balance</span><span class="w"> </span><span class="o">>=</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">balance</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="n">amount</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">transactions</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">Transaction</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">amount</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">date</span>: <span class="nc">Utc</span>::<span class="n">now</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="s">"Insufficient funds"</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">client</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Client</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">name</span>: <span class="s">"John Doe"</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">accounts</span>: <span class="nc">vec</span><span class="o">!</span><span class="p">[</span><span class="n">Account</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">balance</span>: <span class="mi">0</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">transactions</span>: <span class="nc">vec</span><span class="o">!</span><span class="p">[],</span><span class="w"></span>
<span class="w"> </span><span class="p">}],</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="n">client</span><span class="p">.</span><span class="n">accounts</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">deposit</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">client</span><span class="p">.</span><span class="n">accounts</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">withdraw</span><span class="p">(</span><span class="mi">50</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">client</span><span class="p">.</span><span class="n">accounts</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">withdraw</span><span class="p">(</span><span class="mi">50</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Ce code utilise des structures pour représenter les concepts du domaine (compte bancaire, transaction et client), ainsi que
des fonctions pour manipuler ces structures (dépôt, retrait, etc.).</p>
</div>
<div class="section" id="comment-le-ddd-peut-ameliorer-la-collaboration-avec-les-experts-du-domaine">
<h2>4) Comment le DDD peut améliorer la collaboration avec les experts du domaine ?</h2>
<p>En utilisant le DDD, tu peux également collaborer plus efficacement avec les experts du domaine pour obtenir des résultats
plus précis. En comprenant les concepts et les relations qui existent dans le domaine d'application, tu peux facilement poser
des questions aux experts pour obtenir des informations précises sur le fonctionnement du domaine et sur les besoins des
utilisateurs.</p>
<p>De plus, en utilisant des structures et des fonctions qui reflètent les concepts du domaine, tu peux facilement montrer ton
travail aux experts pour obtenir leur avis et leur feedback. Cela peut t'aider à améliorer la qualité de ton application et à
la rendre plus conforme aux exigences du domaine.</p>
<p>En utilisant le DDD, tu peux donc créer des applications en Rust qui sont plus précises et plus adaptées aux besoins des
utilisateurs, ce qui peut augmenter la satisfaction des clients et la réussite de ton projet.</p>
</div>
<div class="section" id="comment-integrer-le-ddd-dans-un-projet-en-rust-existant">
<h2>5) Comment intégrer le DDD dans un projet en Rust existant ?</h2>
<p>Si tu as déjà un projet en Rust qui ne suit pas le DDD, tu peux tout de même l'intégrer à ton processus de développement.
Pour ce faire, tu peux commencer par analyser ton code pour identifier les concepts et les relations qui existent dans le
domaine d'application. Cela te permettra de mieux comprendre le fonctionnement de ton application et de repérer les zones qui
pourraient être améliorées en utilisant le DDD.</p>
<p>Ensuite, tu peux créer des structures et des fonctions en Rust qui reflètent ces concepts et ces relations, et les intégrer
dans ton code existant. Cela peut te permettre de rendre ton code plus clair et plus facile à comprendre, ainsi que de
résoudre les problèmes de manière plus efficace.</p>
<p>Il est important de noter que l'intégration du DDD dans un projet existant peut être un processus long et complexe, et il est
recommandé de le faire étape par étape pour éviter de perturber le fonctionnement de ton application. En prenant le temps de
comprendre le domaine d'application et en utilisant des structures et des fonctions adaptées, tu peux facilement intégrer le
DDD dans ton projet en Rust.</p>
</div>
<div class="section" id="un-exemple-rust-plus-avance-utilisant-le-ddd-un-systeme-de-gestion-de-stock">
<h2>6) Un exemple Rust plus avancé utilisant le DDD : un système de gestion de stock.</h2>
<p>Voici un exemple de code Rust plus avancé qui utilise le DDD pour gérer un système de gestion de stock :</p>
<div class="highlight"><pre><span></span><span class="k">use</span><span class="w"> </span><span class="n">chrono</span>::<span class="p">{</span><span class="n">DateTime</span><span class="p">,</span><span class="w"> </span><span class="n">Utc</span><span class="p">};</span><span class="w"></span>
<span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">collections</span>::<span class="n">HashMap</span><span class="p">;</span><span class="w"></span>
<span class="c1">// Représente un produit dans le stock</span>
<span class="cp">#[derive(Clone, Debug)]</span><span class="w"></span>
<span class="k">struct</span> <span class="nc">Product</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Nom du produit</span>
<span class="w"> </span><span class="n">name</span>: <span class="nb">String</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Quantité en stock</span>
<span class="w"> </span><span class="n">quantity</span>: <span class="kt">u32</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Prix de vente</span>
<span class="w"> </span><span class="n">price</span>: <span class="kt">f32</span><span class="p">,</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="c1">// Représente un client dans le système</span>
<span class="cp">#[derive(Clone, Debug)]</span><span class="w"></span>
<span class="k">struct</span> <span class="nc">Customer</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Nom du client</span>
<span class="w"> </span><span class="n">name</span>: <span class="nb">String</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Adresse email du client</span>
<span class="w"> </span><span class="n">email</span>: <span class="nb">String</span><span class="p">,</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="c1">// Représente une commande dans le système</span>
<span class="cp">#[derive(Clone, Debug)]</span><span class="w"></span>
<span class="k">struct</span> <span class="nc">Order</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Identifiant unique de la commande</span>
<span class="w"> </span><span class="n">id</span>: <span class="kt">u32</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Produits commandés</span>
<span class="w"> </span><span class="n">products</span>: <span class="nb">Vec</span><span class="o"><</span><span class="n">Product</span><span class="o">></span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Client qui a passé la commande</span>
<span class="w"> </span><span class="n">customer</span>: <span class="nc">Customer</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Date de la commande</span>
<span class="w"> </span><span class="n">date</span>: <span class="nc">DateTime</span><span class="o"><</span><span class="n">Utc</span><span class="o">></span><span class="p">,</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="c1">// Représente un système de gestion de stock</span>
<span class="cp">#[derive(Debug)]</span><span class="w"></span>
<span class="k">struct</span> <span class="nc">StockSystem</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Produits en stock</span>
<span class="w"> </span><span class="n">products</span>: <span class="nc">HashMap</span><span class="o"><</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="n">Product</span><span class="o">></span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Clients enregistrés dans le système</span>
<span class="w"> </span><span class="n">customers</span>: <span class="nc">HashMap</span><span class="o"><</span><span class="nb">String</span><span class="p">,</span><span class="w"> </span><span class="n">Customer</span><span class="o">></span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Commandes enregistrées dans le système</span>
<span class="w"> </span><span class="n">orders</span>: <span class="nb">Vec</span><span class="o"><</span><span class="n">Order</span><span class="o">></span><span class="p">,</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="k">impl</span><span class="w"> </span><span class="n">StockSystem</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Ajoute un produit au stock</span>
<span class="w"> </span><span class="k">fn</span> <span class="nf">add_product</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">product</span>: <span class="nc">Product</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">products</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="n">product</span><span class="p">.</span><span class="n">name</span><span class="p">.</span><span class="n">clone</span><span class="p">(),</span><span class="w"> </span><span class="n">product</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Ajoute un client au système</span>
<span class="w"> </span><span class="k">fn</span> <span class="nf">add_customer</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">customer</span>: <span class="nc">Customer</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">customers</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="n">customer</span><span class="p">.</span><span class="n">email</span><span class="p">.</span><span class="n">clone</span><span class="p">(),</span><span class="w"> </span><span class="n">customer</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Passe une commande pour un client donné</span>
<span class="w"> </span><span class="k">fn</span> <span class="nf">place_order</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">products</span>: <span class="nb">Vec</span><span class="o"><</span><span class="n">Product</span><span class="o">></span><span class="p">,</span><span class="w"> </span><span class="n">customer_email</span>: <span class="kp">&</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-> <span class="nb">Result</span><span class="o"><</span><span class="n">Order</span><span class="p">,</span><span class="w"> </span><span class="o">&'</span><span class="nb">static</span><span class="w"> </span><span class="kt">str</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Vérifie si les produits demandés sont en stock</span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">product</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">&</span><span class="n">products</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stock_product</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">products</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="o">&</span><span class="n">product</span><span class="p">.</span><span class="n">name</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">stock_product</span><span class="p">.</span><span class="n">is_none</span><span class="p">()</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">stock_product</span><span class="p">.</span><span class="n">unwrap</span><span class="p">().</span><span class="n">quantity</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="n">product</span><span class="p">.</span><span class="n">quantity</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="s">"Product out of stock"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Vérifie si le client existe dans le système</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">customer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">customers</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">customer_email</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">customer</span><span class="p">.</span><span class="n">is_none</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="s">"Customer not found"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Réduit la quantité en stock pour les produits commandés</span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">product</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">&</span><span class="n">products</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">stock_product</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">products</span><span class="p">.</span><span class="n">get_mut</span><span class="p">(</span><span class="o">&</span><span class="n">product</span><span class="p">.</span><span class="n">name</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">stock_product</span><span class="p">.</span><span class="n">quantity</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="n">product</span><span class="p">.</span><span class="n">quantity</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Crée la commande</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">order</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Order</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">id</span>: <span class="nc">self</span><span class="p">.</span><span class="n">orders</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">u32</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">products</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">customer</span>: <span class="nc">customer</span><span class="p">.</span><span class="n">unwrap</span><span class="p">().</span><span class="n">clone</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">date</span>: <span class="nc">Utc</span>::<span class="n">now</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Ajoute la commande au système</span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">orders</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">order</span><span class="p">.</span><span class="n">clone</span><span class="p">());</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(</span><span class="n">order</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Pour utiliser ce code, tu peux créer une instance de la structure StockSystem et ajouter des produits, des clients et passer
des commandes :</p>
<div class="highlight"><pre><span></span><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Crée une instance du système de stock</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">stock_system</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">StockSystem</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">products</span>: <span class="nc">HashMap</span>::<span class="n">new</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">customers</span>: <span class="nc">HashMap</span>::<span class="n">new</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">orders</span>: <span class="nb">Vec</span>::<span class="n">new</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Ajoute des produits au stock</span>
<span class="w"> </span><span class="n">stock_system</span><span class="p">.</span><span class="n">add_product</span><span class="p">(</span><span class="n">Product</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">name</span>: <span class="s">"Table"</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">quantity</span>: <span class="mi">10</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">price</span>: <span class="mf">100.0</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="w"> </span><span class="n">stock_system</span><span class="p">.</span><span class="n">add_product</span><span class="p">(</span><span class="n">Product</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">name</span>: <span class="s">"Chair"</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">quantity</span>: <span class="mi">20</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">price</span>: <span class="mf">50.0</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Ajoute des clients au système</span>
<span class="w"> </span><span class="n">stock_system</span><span class="p">.</span><span class="n">add_customer</span><span class="p">(</span><span class="n">Customer</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">name</span>: <span class="s">"John Doe"</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">email</span>: <span class="s">"john.doe@example.com"</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="w"> </span><span class="n">stock_system</span><span class="p">.</span><span class="n">add_customer</span><span class="p">(</span><span class="n">Customer</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">name</span>: <span class="s">"Jane Doe"</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">email</span>: <span class="s">"jane.doe@example.com"</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="p">});</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Passe une commande pour un client</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">order</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stock_system</span><span class="p">.</span><span class="n">place_order</span><span class="p">(</span><span class="fm">vec!</span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="n">Product</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">name</span>: <span class="s">"Table"</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">quantity</span>: <span class="mi">1</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">price</span>: <span class="mf">100.0</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="n">Product</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">name</span>: <span class="s">"Chair"</span><span class="p">.</span><span class="n">to_string</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">quantity</span>: <span class="mi">2</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">price</span>: <span class="mf">50.0</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"> </span><span class="s">"jane.doe@example.com"</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">"Order placed: {:?}"</span><span class="p">,</span><span class="w"> </span><span class="n">order</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Affiche les informations du système</span>
<span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">"Stock system: {:?}"</span><span class="p">,</span><span class="w"> </span><span class="n">stock_system</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</div>
<div class="section" id="avantages-et-defis-du-ddd-dans-un-projet-en-rust">
<h2>1) Avantages et défis du DDD dans un projet en Rust.</h2>
<p>En utilisant le DDD dans tes projets en Rust, tu peux bénéficier de nombreux avantages, tels que :</p>
<ul class="simple">
<li>Des logiciels plus clairs et plus faciles à comprendre, ce qui peut améliorer la collaboration avec les autres développeurs et les experts du domaine.</li>
<li>Des modèles de données et des algorithmes qui reflètent les concepts et les relations du domaine d'application, ce qui peut améliorer la qualité de ton code et la précision de tes résultats.</li>
<li>Un code plus facile à maintenir et à évoluer, ce qui peut réduire les coûts de développement et accélérer les délais de mise sur le marché.</li>
</ul>
<p>Cependant, le DDD peut également présenter des défis, tels que :</p>
<ul class="simple">
<li>La nécessité de comprendre en profondeur le domaine d'application, ce qui peut être difficile et prendre du temps pour les développeurs qui ne sont pas des experts du domaine.</li>
<li>L'intégration du DDD dans un projet existant peut être complexe et perturbante pour le fonctionnement de l'application.</li>
<li>La mise en place d'un processus de développement orienté domaines peut nécessiter des changements importants dans la façon dont les équipes de développement travaillent, ce qui peut être difficile à gérer.</li>
</ul>
</div>
Smart Contract Elrond en Rust : Multiple NFTs staking2022-10-16T00:00:00+02:002022-10-16T00:00:00+02:00Morgantag:dotmobo.github.io,2022-10-16:/elrond-sc-rust-multiple-nfts-staking.html<p class="first last">Smart Contract Elrond en Rust : Multiple NFTs staking</p>
<img alt="Elrond" class="align-right" src="./images/elrond.png" />
<p>C'est parti pour un petit article faisant suite au premier dédié <a class="reference external" href="https://dotmobo.github.io/elrond-sc-rust-nft-staking.html#elrond-sc-rust-nft-staking">au staking de NFT sur Elrond</a>.</p>
<div class="section" id="contexte">
<h2>Contexte</h2>
<p>Afin d'avoir un premier jet fonctionnel, on a vu comment staker un NFT sur un smart contract avec quelques limitations :</p>
<ul class="simple">
<li>Chaque utilisateur représenté par une adresse Elrond ne peut envoyer qu'un seul NFT dans la pool.</li>
<li>Les récompenses sont à réclamer régulièrement et ne sont pas directement attribuées.</li>
<li>Une seule collection de NFT est supportée. Donc pour faire une pool de staking sur une autre collection, il
faudra déployer un deuxième SC.</li>
</ul>
<p>On va ici voir comment faire sauter la première limitation, c'est-à-dire de permettre le staking de plusieurs NFTs par adresse Elrond dans la pool.</p>
<p>Dans ta <strong>dapp</strong>, tu pourras ainsi faire quelque-chose comme ça :</p>
<img alt="Elrond" src="./images/staking_multiple.png" />
</div>
<div class="section" id="smart-contract">
<h2>Smart Contract</h2>
<p>On reprend donc <a class="reference external" href="https://github.com/dotmobo/dbc-dashboard/blob/master/contract/nft_staking/src/empty.rs">le code du smart contract précédent</a>, et on va ajouter une nouvelle fonctionnalité : le staking de plusieurs NFTs.</p>
<p>Tu commences donc par modifier le fichier <strong>src/stake_info.rs</strong> de la manière suivante :</p>
<div class="highlight"><pre><span></span><span class="n">elrond_wasm</span>::<span class="n">imports</span><span class="o">!</span><span class="p">();</span><span class="w"></span>
<span class="n">elrond_wasm</span>::<span class="n">derive_imports</span><span class="o">!</span><span class="p">();</span><span class="w"></span>
<span class="k">use</span><span class="w"> </span><span class="n">elrond_wasm</span>::<span class="n">types</span>::<span class="n">heap</span>::<span class="nb">Vec</span><span class="p">;</span><span class="w"></span>
<span class="cp">#[derive(TypeAbi, TopEncode, TopDecode, PartialEq, Debug)]</span><span class="w"></span>
<span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">StakeInfo</span><span class="o"><</span><span class="n">M</span>: <span class="nc">ManagedTypeApi</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">pub</span><span class="w"> </span><span class="n">address</span>: <span class="nc">ManagedAddress</span><span class="o"><</span><span class="n">M</span><span class="o">></span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">pub</span><span class="w"> </span><span class="n">nft_nonce</span>: <span class="nb">Vec</span><span class="o"><</span><span class="kt">u64</span><span class="o">></span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">pub</span><span class="w"> </span><span class="n">lock_time</span>: <span class="kt">u64</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">pub</span><span class="w"> </span><span class="n">unstake_time</span>: <span class="kt">u64</span><span class="p">,</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>L'astuce ici, c'est d'avoir transformé le champ <strong>nft_nonce</strong> en un vecteur de <strong>u64</strong>. Cela permet de stocker plusieurs valeurs dans ce champ.</p>
<p>Tu va maintenant modifier le fichier <strong>src/empty.rs</strong> pour prendre en compte cette modification et permettre la gestion de plusieurs NFTs par adresse.</p>
<p>Au niveau du bloc d'initialisation, on a juste rajouté un nouveau paramètre appelé <strong>nbr_of_nft_staked</strong> qui va nous permettre de stocker le nombre
total de NFTs stakés, pour donner une information globale du contract à l'utilisateur.</p>
<div class="highlight"><pre><span></span><span class="cp">#![no_std]</span><span class="w"></span>
<span class="n">elrond_wasm</span>::<span class="n">imports</span><span class="o">!</span><span class="p">();</span><span class="w"></span>
<span class="k">use</span><span class="w"> </span><span class="n">elrond_wasm</span>::<span class="n">types</span>::<span class="n">heap</span>::<span class="nb">Vec</span><span class="p">;</span><span class="w"></span>
<span class="k">mod</span> <span class="nn">stake_info</span><span class="p">;</span><span class="w"></span>
<span class="k">use</span><span class="w"> </span><span class="n">stake_info</span>::<span class="n">StakeInfo</span><span class="p">;</span><span class="w"></span>
<span class="cp">#[elrond_wasm::contract]</span><span class="w"></span>
<span class="k">pub</span><span class="w"> </span><span class="k">trait</span><span class="w"> </span><span class="n">NftStaking</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="cp">#[init]</span><span class="w"></span>
<span class="w"> </span><span class="k">fn</span> <span class="nf">init</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">nft_identifier</span>: <span class="nc">EgldOrEsdtTokenIdentifier</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">minimum_staking_days</span>: <span class="kt">u64</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">rewards_token_id</span>: <span class="nc">EgldOrEsdtTokenIdentifier</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">rewards_token_amount_per_day</span>: <span class="nc">BigUint</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">rewards_token_total_supply</span>: <span class="nc">BigUint</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nft_identifier</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">nft_identifier</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">minimum_staking_days</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">minimum_staking_days</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_id</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">rewards_token_id</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_amount_per_day</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">rewards_token_amount_per_day</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_total_supply</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">rewards_token_total_supply</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// if staking status is empty, set it to false</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_status</span><span class="p">().</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_status</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">// if staking end time is empty, set it to 0</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_end_time</span><span class="p">().</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_end_time</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">// if nbr of stakers is empty, set it to 0</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">// if nbr of nft staked is empty, set it to 0</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_nft_staked</span><span class="p">().</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_nft_staked</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
</pre></div>
<p>Au niveau des <strong>storage_mapper</strong> et des <strong>view</strong>, tu peux déjà rajouter une <strong>view</strong> pour le nouveau champ
<strong>getNbrOfNftStaked</strong>. Tu va également modifier la méthode <strong>getNftNonce</strong> pour qu'elle renvoie un vecteur de <strong>u64</strong>.
Enfin, tu calcules le montant des récompenses à envoyer à l'utilisateur en fonction du nombre de NFTs stakés dans <strong>getCurrentRewards</strong>.</p>
<div class="highlight"><pre><span></span><span class="cp">#[view(getCurrentRewards)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">get_current_rewards</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">address</span>: <span class="kp">&</span><span class="nc">ManagedAddress</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">BigUint</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">cur_time</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_block_timestamp</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="c1">// calculate rewards</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">from_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cur_time</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_status</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">from_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_end_time</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">staked_days</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="k">u64</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">from_time</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">lock_time</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">staked_days</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">from_time</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">lock_time</span><span class="p">)</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">86400</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">rewards_amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_amount_per_day</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">staked_days</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">nft_nonce</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">u64</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">rewards_amount</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="cp">#[view(getNftNonce)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">get_nft_nonce</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">address</span>: <span class="kp">&</span><span class="nc">ManagedAddress</span><span class="p">)</span><span class="w"> </span>-> <span class="nb">Vec</span><span class="o"><</span><span class="kt">u64</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">nft_nonce</span>: <span class="nb">Vec</span><span class="o"><</span><span class="kt">u64</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">nft_nonce</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">nft_nonce</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="cp">#[view(getNbrOfNftStaked)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"nbr_of_nft_staked"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">nbr_of_nft_staked</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="kt">u64</span><span class="o">></span><span class="p">;</span><span class="w"></span>
</pre></div>
<p>Tu modifies la méthode <strong>stake</strong> pour prendre en compte non plus un seul paiement, c'est-à-dire un seul NFT, mais plusieurs paiements.
C'est ici que tu vas donc construire le vecteur de NFTs stakés par l'utilisateur. Penses également à incrémenter ta nouvelle variable <strong>nbr_of_nft_staked</strong>.</p>
<div class="highlight"><pre><span></span><span class="cp">#[payable(</span><span class="s">"*"</span><span class="cp">)]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">stake</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">payments</span>: <span class="nc">ManagedVec</span><span class="o"><</span><span class="n">EsdtTokenPayment</span><span class="o"><</span><span class="bp">Self</span>::<span class="n">Api</span><span class="o">>></span><span class="w"> </span><span class="o">=</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">call_value</span><span class="p">().</span><span class="n">all_esdt_transfers</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_status</span><span class="p">().</span><span class="n">get</span><span class="p">(),</span><span class="w"> </span><span class="s">"The staking is stopped"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">payment</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">&</span><span class="n">payments</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">payment</span><span class="p">.</span><span class="n">token_identifier</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nft_identifier</span><span class="p">().</span><span class="n">get</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="s">"Invalid nft identifier"</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="n">payment</span><span class="p">.</span><span class="n">token_nonce</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="s">"Invalid nft nonce"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="n">payment</span><span class="p">.</span><span class="n">amount</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s">"You can only send 1 nft"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">caller</span>: <span class="nc">ManagedAddress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">cur_time</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_block_timestamp</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">unstake_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cur_time</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">minimum_staking_days</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">86400</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">vec_nonce</span>: <span class="nb">Vec</span><span class="o"><</span><span class="kt">u64</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">payment</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">&</span><span class="n">payments</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">vec_nonce</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">payment</span><span class="p">.</span><span class="n">token_nonce</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">StakeInfo</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">address</span>: <span class="nc">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">nft_nonce</span>: <span class="nc">vec_nonce</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">lock_time</span>: <span class="nc">cur_time</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">unstake_time</span>: <span class="nc">unstake_time</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">())</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">stake_info</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">payment</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">&</span><span class="n">payments</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">vec_nonce</span>: <span class="nb">Vec</span><span class="o"><</span><span class="kt">u64</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">nft_nonce</span><span class="p">.</span><span class="n">clone</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">vec_nonce</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">payment</span><span class="p">.</span><span class="n">token_nonce</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">nft_nonce</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">vec_nonce</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">lock_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cur_time</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">unstake_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">unstake_time</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">stake_info</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_nft_staked</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_nft_staked</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">payments</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">u64</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Pour la méthode <strong>unstake</strong>, il suffira de parcourir le vecteur de NFTs stakés par l'utilisateur et de les transférer à l'utilisateur un par un.
Attention, si beaucoup de NFTs sont stakés, cela risque de consommer beaucoup de gas.</p>
<div class="highlight"><pre><span></span><span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">unstake</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">caller</span>: <span class="nc">ManagedAddress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">cur_time</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_block_timestamp</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">unstake_time</span><span class="w"> </span><span class="o"><=</span><span class="w"> </span><span class="n">cur_time</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s">"You can't unlock staking nft yet."</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">nft_identifier</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nft_identifier</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">nft_nonce</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">nft_nonce</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">nbr_of_nonce</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="n">nft_nonce</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">u64</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">BigUint</span>::<span class="n">from</span><span class="p">(</span><span class="mi">1</span><span class="k">u32</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// for each nft nonce, send nft back to caller</span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">nft_nonce</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">send</span><span class="p">().</span><span class="n">direct</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">caller</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">nft_identifier</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">n</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">amount</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">clear</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">>=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_nft_staked</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">>=</span><span class="w"> </span><span class="n">nbr_of_nonce</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_nft_staked</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_nft_staked</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">nbr_of_nonce</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_nft_staked</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Enfin, tu corriges la méthode <strong>claim</strong> pour calculer les récompenses en fonction du nombre de NFTs stakés, comme pour la <strong>view</strong> précédente appelée <strong>getCurrentRewards</strong>.</p>
<div class="highlight"><pre><span></span><span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">claim</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">caller</span>: <span class="nc">ManagedAddress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">cur_time</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_block_timestamp</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">rewards_token_total_supply</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_total_supply</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">nft_nonce</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">nft_nonce</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">unstake_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">unstake_time</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">reward_token_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_id</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="c1">// calculate rewards</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">from_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cur_time</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_status</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">from_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_end_time</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">staked_days</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="k">u64</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">from_time</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">lock_time</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">staked_days</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">from_time</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">lock_time</span><span class="p">)</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">86400</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">rewards_amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_amount_per_day</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">staked_days</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">nft_nonce</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">u64</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">// check the supply</span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">rewards_amount</span><span class="w"> </span><span class="o"><=</span><span class="w"> </span><span class="n">rewards_token_total_supply</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s">"You can't claim rewards more than total supply."</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// send rewards</span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">send</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">direct</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">reward_token_id</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">rewards_amount</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// remove rewards amount from rewards_token_total_supply</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">rewards_token_total_supply</span><span class="w"> </span><span class="o">>=</span><span class="w"> </span><span class="n">rewards_amount</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_total_supply</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="p">(</span><span class="n">rewards_token_total_supply</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">rewards_amount</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_total_supply</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">BigUint</span>::<span class="n">from</span><span class="p">(</span><span class="mi">0</span><span class="k">u32</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">// update staking_info</span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">clear</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">StakeInfo</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">address</span>: <span class="nc">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">nft_nonce</span>: <span class="nc">nft_nonce</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">lock_time</span>: <span class="nc">from_time</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">unstake_time</span>: <span class="nc">unstake_time</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">())</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">stake_info</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Les autres <strong>storage_mapper</strong>, <strong>view</strong> et <strong>owner endpoint</strong> restent inchangés :</p>
<div class="highlight"><pre><span></span><span class="cp">#[view(getLockTime)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">get_lock_time</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">address</span>: <span class="kp">&</span><span class="nc">ManagedAddress</span><span class="p">)</span><span class="w"> </span>-> <span class="kt">u64</span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">lock_time</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">lock_time</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">lock_time</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="cp">#[view(getUnstakeTime)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">get_unstake_time</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">address</span>: <span class="kp">&</span><span class="nc">ManagedAddress</span><span class="p">)</span><span class="w"> </span>-> <span class="kt">u64</span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">unstake_time</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">unstake_time</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">unstake_time</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="cp">#[view(getNftIdentifier)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"nft_identifier"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">nft_identifier</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">EgldOrEsdtTokenIdentifier</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getMinimumStakingDays)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"minimum_staking_days"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">minimum_staking_days</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="kt">u64</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getRewardsTokenId)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"rewards_token_id"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">rewards_token_id</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">EgldOrEsdtTokenIdentifier</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getRewardsTokenAmountPerDay)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"rewards_token_amount_per_day"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">rewards_token_amount_per_day</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">BigUint</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getStakingInfo)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"staking_info"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">staking_info</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">address</span>: <span class="kp">&</span><span class="nc">ManagedAddress</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">StakeInfo</span><span class="o"><</span><span class="bp">Self</span>::<span class="n">Api</span><span class="o">>></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getStakingStatus)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"staking_status"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">staking_status</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="kt">bool</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getStakingEndTime)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"staking_end_time"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">staking_end_time</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="kt">u64</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getRewardsTokenTotalSupply)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"rewards_token_total_supply"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">rewards_token_total_supply</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">BigUint</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getNbrOfStakers)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"nbr_of_stakers"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">nbr_of_stakers</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="kt">u64</span><span class="o">></span><span class="p">;</span><span class="w"></span>
</pre></div>
<p>Tu re-compiles alors le tout avec <strong>erdpy</strong> :</p>
<div class="highlight"><pre><span></span>erdpy contract build
</pre></div>
<p>Le code final est visible <a class="reference external" href="https://github.com/dotmobo/dbc-dashboard/blob/master/contract/multiple_nft_staking/src/empty.rs">ici</a>.</p>
</div>
<div class="section" id="deploiement">
<h2>Déploiement</h2>
<p>Ton fichier <a class="reference external" href="https://github.com/dotmobo/dbc-dashboard/blob/master/contract/multiple_nft_staking/erdpy.json">erdpy.json</a> ne bouge pas, c'est exactement le même que pour le déploiement du contrat précédent.
Tu peux donc déployer et faire tes transactions :</p>
<div class="highlight"><pre><span></span>erdpy contract deploy
erdpy tx new --help
</pre></div>
<p>Concernant <a class="reference external" href="https://github.com/dotmobo/dbc-dashboard/blob/master/dapp/src/components/MultipleNftStaking/index.tsx">l'interface frontend pour le SC</a>,
tu peux suivre mon exemple ou faire ce qu'il te plait !</p>
</div>
Environnement de développement en 20222022-09-19T00:00:00+02:002022-09-19T00:00:00+02:00Morgantag:dotmobo.github.io,2022-09-19:/environnement-developpement-2022.html<p class="first last">Environnement de développement 2022</p>
<p>Mon dernier article concernant mon environnement de développement datant de 2016, il était temps de faire une petite
mise à jour de ce qui a changé depuis.</p>
<img alt="Debian" class="align-right" src="./images/debian.png" />
<div class="section" id="distribution">
<h2>Distribution</h2>
<p>Au revoir Arch Linux, et bonjour <a class="reference external" href="https://www.debian.org/">Debian Sid</a>. J'adore toujours Arch Linux mais je n'ai plus autant de temps qu'avant pour
bidouiller et customiser mon système. Il me faut un système stable et robuste, sans surprise, mais qui soit relativement
à jour par rapport aux derniers paquets disponibles. Et clé en main.</p>
<p>Ça va faire 3 ans que je suis sur Debian Sid et je n'ai jamais eu de problème. C'est un bon compromis entre stabilité et
fraicheur. Debian est devenu suffisamment user-friendly pour rendre obsolète toutes les distros basées sur elle comme Ubuntu
ou Mint.</p>
<p>Tout ce qui me dérange un peu, c'est la migration vers Systemd, car je ne suis personnellement pas fan du principe.
Je préfère les outils qui suivent plus la philosophie Unix, c'est-à-dire qui font une seule chose et le font bien.
Mais bon, toutes les grosses distros ont désormais migré dessus, et à l'avenir il faudra peut-être plutôt regarder du
côté de BSD pour avoir des outils plus simples.</p>
</div>
<div class="section" id="bureau">
<h2>Bureau</h2>
<p><a class="reference external" href="https://www.gnome.org/">Gnome</a>. Principalement parce que je suis passé d'un PC fixe à un portable avec un dock. Ce qui fait que j'ai beaucoup
de périphériques à gérer et que je me connecte/déconnecte régulièrement à différents docks et écrans. Et Gnome gère
automatiquement tout ça pour moi. Ok, y a plein de daemons qui tournent mais bon... C'est clairement du confort.</p>
<p>Je regarde toujours les tiling window managers de temps en temps comme i3, et j'ai donc testé Pop Shell qui permet du tiling avec Gnome.
C'est pas trop mal mais finalement, sur un portable, je préfére utiliser Gnome avec le terminal top-down <a class="reference external" href="http://guake-project.org/">Guake</a>.</p>
</div>
<div class="section" id="shell">
<h2>Shell</h2>
<p>J'ai migré de zsh à <a class="reference external" href="https://fishshell.com/">fish</a> après m'être rendu compte que ma configuration customisée de zsh correspondait exactement
au réglage par défaut de fish. Donc fish vanilla couplé à <a class="reference external" href="https://github.com/tmux/tmux">tmux</a> pour la gestion des onglets et des sessions.
Rien à configurer, juste ça marche par défaut. Magique.</p>
</div>
<div class="section" id="theme">
<h2>Theme</h2>
<p>Concernant les thèmes, je suis passé sur <a class="reference external" href="https://draculatheme.com/">Dracula</a> pour absolument tout. C'est un thème sombre parfait qui est disponible
pour gnome, fish, vim, tmux, vs code, etc. L'ensemble de mon environnement est donc cohérent et j'aime beaucoup le résultat.</p>
</div>
<div class="section" id="police">
<h2>Police</h2>
<p>J'utile désormais la police <a class="reference external" href="https://github.com/tonsky/FiraCode">Fira Code</a> depuis que je l'ai découverte. C'est une police monospace avec des ligatures,
parfaite pour le développement.</p>
</div>
<div class="section" id="editeur-de-code">
<h2>Editeur de code</h2>
<p><a class="reference external" href="https://code.visualstudio.com/">Visual Code Studio</a> et <a class="reference external" href="https://neovim.io/">Neovim</a>.</p>
<p>VS Code a clairement remporté la bataille des éditeurs de texte face à Atom et Sublime Text.
C'est un éditeur de code moderne, performant, avec un grand nombre de plugins. Et il est open source.
Rien à redire de ce côté là, bravo Microsoft pour une fois.</p>
<p>Neovim prend bien la relève de Vim et c'est plutôt agréable de pouvoir utiliser Lua pour gérer les plugins.</p>
</div>
<div class="section" id="completion">
<h2>Completion</h2>
<p>J'ai participé à la beta de <a class="reference external" href="https://github.com/features/copilot">Github Copilot</a> et j'y suis resté depuis. Un outil vraiment bluffant, et qui continue de me
bluffer tous les jours. Je vois beaucoup de réticents mais vraiment... essayez-le, au moins pour votre culture.
Pour des développements type Java avec une tonne de boilerplate à écrire, c'est juste génial.</p>
</div>
<div class="section" id="gestion-des-applications">
<h2>Gestion des applications</h2>
<p>Récemment j'ai découvert <a class="reference external" href="https://flatpak.org/">Flatpak</a> et je suis plutôt fan du principe. J'avais testé Snap de Ubuntu à l'époque mais ça ne m'avait pas
convaincu. Flatpak est plutôt rapide et simple à utiliser. C'est un peu plus sécurisé et ça permet de gérer les dépendances de manière plus propre.
Le principe d'isolation me fait un peu penser aux installations des applications sur Android.</p>
<p>Du coup, désormais, dès que je dois installer une application externe propriétaire commme Spotify, Discord, Telegram ou Google Chrome,
je passe par Flatpak.</p>
<p>En vrai, je me dis que Flatpak me permettrait même de rester sur Debian Stable plutôt que Sid et d'avoir des outils à jour grâce
à lui. Mais bon, il y a encore quelques soucis de compatibilité avec certaines applications comme VS Code ou Steam par exemple.</p>
<p>Bonne découverte !</p>
</div>
Smart Contract Elrond en Rust : NFT market2022-07-07T00:00:00+02:002022-07-07T00:00:00+02:00Morgantag:dotmobo.github.io,2022-07-07:/elrond-sc-rust-nft-market.html<p class="first last">Smart Contract Elrond en Rust : NFT market</p>
<img alt="Elrond" class="align-right" src="./images/elrond.png" />
<p>C'est parti pour le troisième épisode de la série sur les Smart Contracts <a class="reference external" href="https://elrond.com/">Elrond</a>
en <a class="reference external" href="https://rust-lang.org/">Rust</a>. Après avoir vu comment <a class="reference external" href="http://dotmobo.github.io/elrond-sc-rust-nft-staking.html#elrond-sc-rust-nft-staking">staker un NFT</a> pour gagner des $DEAD et comment
utiliser ces $DEAD pour <a class="reference external" href="http://dotmobo.github.io/elrond-sc-rust-dao-vote.html#elrond-sc-rust-dao-vote">voter dans notre DAO</a>, on va pouvoir désormais acheter des NFTs sur notre propre
<em>marketplace</em> avec des $DEAD.</p>
<div class="section" id="contexte">
<h2>Contexte</h2>
<p>On doit pouvoir envoyer des NFTs sur un Smart Contract pour ainsi les vendre avec notre propre ESDT,
ici $DEAD.</p>
<p>C'est une version très simplifié d'un <em>marketplace</em> dans le sens où :</p>
<ul class="simple">
<li>on ne va pouvoir vendre les NFTs que d'une seule collection à la fois.</li>
<li>le prix de chaque NFT sera identique.</li>
</ul>
<p>Si on veut avoir des prix différents ou utiliser une autre collection, il faudra soit changer les paramètres
du Smart Contract, soit déployer un autre Smart Contract.</p>
<p>Dans ta <strong>dapp</strong>, ça pourra donner quelque-chose comme ça :</p>
<img alt="Elrond" src="./images/market.jpg" />
</div>
<div class="section" id="smart-contract">
<h2>Smart Contract</h2>
<p>Comme d'habitude, on crée un SC vide à l'aide de <strong>erdpy</strong>.</p>
<div class="highlight"><pre><span></span>erdpy contract new market --template empty
</pre></div>
<p>Et on passe directement à l'écriture du SC dans <strong>src/empty.rs</strong>. Notre fonction d'initialisation
va prendre en paramètre l'identifiant du token à utiliser pour les paiements, le prix d'un NFT ainsi
que l'identifiant de la collection de NFT que l'on souhaite vendre.</p>
<div class="highlight"><pre><span></span><span class="cp">#![no_std]</span><span class="w"></span>
<span class="n">elrond_wasm</span>::<span class="n">imports</span><span class="o">!</span><span class="p">();</span><span class="w"></span>
<span class="sd">/// An empty contract. To be used as a template when starting a new contract from scratch.</span>
<span class="cp">#[elrond_wasm::contract]</span><span class="w"></span>
<span class="k">pub</span><span class="w"> </span><span class="k">trait</span><span class="w"> </span><span class="n">EmptyContract</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="cp">#[init]</span><span class="w"></span>
<span class="w"> </span><span class="k">fn</span> <span class="nf">init</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">token_id</span>: <span class="nc">TokenIdentifier</span><span class="p">,</span><span class="w"> </span><span class="n">price</span>: <span class="nc">BigUint</span><span class="p">,</span><span class="w"> </span><span class="n">nft_identifier</span>: <span class="nc">TokenIdentifier</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">token_id</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">token_id</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">price</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">price</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nft_identifier</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">nft_identifier</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">bank</span><span class="p">().</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">bank</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="n">BigUint</span>::<span class="n">from</span><span class="p">(</span><span class="mi">0</span><span class="k">u32</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>On ajoute dans notre <strong>trait</strong> les <strong>storage_mapper</strong> et <strong>view</strong> des différentes variables que l'on va stocker dans le SC.
L'élément <strong>bank</strong> correspond au montant total de $DEAD que l'on a dans notre SC suite aux divers paiements réalisés.</p>
<div class="highlight"><pre><span></span><span class="cp">#[view(getBank)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"bank"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">bank</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">BigUint</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getTokenId)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"token_id"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">token_id</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">TokenIdentifier</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getNftIdentifier)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"nft_identifier"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">nft_identifier</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">TokenIdentifier</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getPrice)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"price"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">price</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">BigUint</span><span class="o">></span><span class="p">;</span><span class="w"></span>
</pre></div>
<p>Ensuite, on ajoute quelques petites fonctions utilitaires pour l'administrateur du SC.
On lui donne la possibilité de récupérer tous les fonds de la banque, de changer le prix de vente des NFTs
ou de changer l'identifiant de la collection de NFT que l'on souhaite vendre. Attention à ne pas oublier le
<strong>only_owner</strong>, sinon n'importe qui pourrait appeler ces fonctions !</p>
<div class="highlight"><pre><span></span><span class="cp">#[only_owner]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">withdraw</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">caller</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">token_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">token_id</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">bank</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">bank</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">send</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">direct</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">token_id</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">bank</span><span class="p">,</span><span class="w"> </span><span class="s">b"withdraw successful"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// reset the bank</span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">bank</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="n">BigUint</span>::<span class="n">from</span><span class="p">(</span><span class="mi">0</span><span class="k">u32</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="cp">#[only_owner]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">change_price</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">price</span>: <span class="nc">BigUint</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">price</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">price</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="cp">#[only_owner]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">change_nft_identifier</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">nft_identifier</span>: <span class="nc">TokenIdentifier</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nft_identifier</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">nft_identifier</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Enfin, on a la fonction principale de notre SC qui permet d'acheter des NFTs.
On vérifie ici le type du token, le montant envoyé et l'identifiant de la collection de NFT.
Le <strong>nonce</strong> envoyé en paramètre correspond au numéro du NFT que l'utilisateur veut acheter.
Si tout est bon, on lui envoie le NFT via la fonction <strong>self.send().direct()</strong> et on incrémente la banque.</p>
<div class="highlight"><pre><span></span><span class="cp">#[payable(</span><span class="s">"*"</span><span class="cp">)]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">buy</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="cp">#[payment_token]</span><span class="w"> </span><span class="n">payment_token</span>: <span class="nc">TokenIdentifier</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="cp">#[payment_amount]</span><span class="w"> </span><span class="n">payment_amount</span>: <span class="nc">BigUint</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">nft_identifier</span>: <span class="nc">TokenIdentifier</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">nft_nonce</span>: <span class="kt">u64</span><span class="p">,</span><span class="w"></span>
<span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">payment_token</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">token_id</span><span class="p">().</span><span class="n">get</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="s">"Invalid payment token"</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">payment_amount</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">price</span><span class="p">().</span><span class="n">get</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="s">"Invalid payment amount"</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">nft_identifier</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nft_identifier</span><span class="p">().</span><span class="n">get</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="s">"Invalid nft identifier"</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">caller</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">BigUint</span>::<span class="n">from</span><span class="p">(</span><span class="mi">1</span><span class="k">u32</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">send</span><span class="p">().</span><span class="n">direct</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">nft_identifier</span><span class="p">,</span><span class="w"> </span><span class="n">nft_nonce</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">amount</span><span class="w"> </span><span class="p">,</span><span class="w"> </span><span class="s">b"purchase successful"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Add the amount to the bank</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">bank</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">bank</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">bank</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">bank</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">&</span><span class="n">payment_amount</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Tu compiles ton SC avec <strong>erdpy</strong> pour vérifier que tout est ok :</p>
<div class="highlight"><pre><span></span>erdpy contract build
</pre></div>
<p>Tu peux trouver le résultat final <a class="reference external" href="https://github.com/dotmobo/dbc-dashboard/blob/master/contract/buy_serum/src/empty.rs">ici</a>.</p>
</div>
<div class="section" id="deploiement">
<h2>Déploiement</h2>
<p>Comme dans les précédents épisodes, tu configures ton fichier <strong>erdpy.json</strong> pour déployer le SC
sur devnet en lui passant les bons arguments. Le SC doit être payable via <strong>"metadata-payable": true</strong> et
le <strong>pem</strong> de ton <strong>wallet</strong> doit se trouver sur <strong>../../wallet/wallet-owner.pem</strong>.</p>
<p>Concernant les arguments, <strong>DEADBROS-fa8f0f</strong> correspond à l'identifiant du token, <strong>100000000000000000000</strong>
au prix d'un NFT avec 18 decimals et <strong>DEAD1-2d86a5</strong> est l'id de la collection de NFT à vendre.</p>
<div class="highlight"><pre><span></span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"default"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"proxy"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://devnet-api.elrond.com"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"chainID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"D"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nt">"contract"</span><span class="p">:{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"deploy"</span><span class="p">:{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"verbose"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"bytecode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"output/buy_serum.wasm"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"recall-nonce"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"metadata-payable"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"pem"</span><span class="p">:</span><span class="w"> </span><span class="s2">"../../wallet/wallet-owner.pem"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"gas-limit"</span><span class="p">:</span><span class="w"> </span><span class="mi">59999999</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"arguments"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"str:DEADBROS-fa8f0f"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"100000000000000000000"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"str:DEAD1-2d86a5"</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">"send"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"outfile"</span><span class="p">:</span><span class="w"> </span><span class="s2">"deploy-testnet.interaction.json"</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nt">"upgrade"</span><span class="p">:{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"verbose"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"bytecode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"output/buy_serum.wasm"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"recall-nonce"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"metadata-payable"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"pem"</span><span class="p">:</span><span class="w"> </span><span class="s2">"../../wallet/wallet-owner.pem"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"gas-limit"</span><span class="p">:</span><span class="w"> </span><span class="mi">59999999</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"arguments"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"str:DEADBROS-fa8f0f"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"100000000000000000000"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"str:DEAD1-2d86a5"</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">"send"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"outfile"</span><span class="p">:</span><span class="w"> </span><span class="s2">"deploy-testnet.interaction.json"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Tu peux alors déployer ton SC et tester les transactions avec <strong>erdpy</strong>.</p>
<div class="highlight"><pre><span></span>erdpy contract deploy
erdpy tx new --help
</pre></div>
<p>N'oublie pas d'envoyer les NFTs que tu souhaites vendre à l'adresse de ton SC !</p>
</div>
Smart Contract Elrond en Rust : NFT staking2022-07-07T00:00:00+02:002022-07-07T00:00:00+02:00Morgantag:dotmobo.github.io,2022-07-07:/elrond-sc-rust-nft-staking.html<p class="first last">Smart Contract Elrond en Rust : NFT staking</p>
<img alt="Elrond" class="align-right" src="./images/elrond.png" />
<p>Voici le deuxième épisode de cette série sur les Smart Contracts <a class="reference external" href="https://elrond.com/">Elrond</a>
en <a class="reference external" href="https://rust-lang.org/">Rust</a>. On va parler ici d'un
sujet qui intéresse pas mal de monde dans l'univers des NFTs : le <strong>staking</strong>.</p>
<p>On suppose que tu as déjà tous les outils pour te lancer dans l'aventure. Si ce n'est pas le cas, jettes un oeil
au <a class="reference external" href="http://dotmobo.github.io/elrond-sc-rust-dao-vote.html#elrond-sc-rust-dao-vote">premier épisode</a>.</p>
<div class="section" id="contexte">
<h2>Contexte</h2>
<p>C'est une version simple du staking qui est largement améliorable, mais ça permet d'avoir un premier jet
fonctionnel.</p>
<p>L'idée ici est de créer un SC qui va être une pool de staking. Chaque utilisateur va pouvoir envoyer un
NFT dans cette pool, et pourra réclamer journalièrement des tokens ESDT en récompense.</p>
<p>On a deux limitations dans cette version :</p>
<ul class="simple">
<li>Chaque utilisateur représenté par une adresse Elrond ne peut envoyer qu'un seul NFT dans la pool.</li>
<li>Les récompenses sont à réclamer régulièrement et ne sont pas directement attribuées.</li>
<li>Une seule collection de NFT est supportée. Donc pour faire une pool de staking sur une autre collection, il
faudra déployer un deuxième SC.</li>
</ul>
<p>Dans ta <strong>dapp</strong>, ça pourra ressembler à ça par exemple :</p>
<img alt="Elrond" src="./images/staking.png" />
</div>
<div class="section" id="smart-contract">
<h2>Smart Contract</h2>
<p>Comme dans le premier épisode, on crée un SC vide à l'aide de <strong>erdpy</strong>.</p>
<div class="highlight"><pre><span></span>erdpy contract new staking --template empty
</pre></div>
<p>Ensuite, dans un fichier <strong>src/stake_info.rs</strong>, on va avoir notre structure qui nous permettra de stocker les
informations de staking de l'utilisateur.</p>
<div class="highlight"><pre><span></span><span class="n">elrond_wasm</span>::<span class="n">imports</span><span class="o">!</span><span class="p">();</span><span class="w"></span>
<span class="n">elrond_wasm</span>::<span class="n">derive_imports</span><span class="o">!</span><span class="p">();</span><span class="w"></span>
<span class="cp">#[derive(TypeAbi, TopEncode, TopDecode, PartialEq, Debug)]</span><span class="w"></span>
<span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">StakeInfo</span><span class="o"><</span><span class="n">M</span>: <span class="nc">ManagedTypeApi</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">pub</span><span class="w"> </span><span class="n">address</span>: <span class="nc">ManagedAddress</span><span class="o"><</span><span class="n">M</span><span class="o">></span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">pub</span><span class="w"> </span><span class="n">nft_nonce</span>: <span class="kt">u64</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">pub</span><span class="w"> </span><span class="n">lock_time</span>: <span class="kt">u64</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">pub</span><span class="w"> </span><span class="n">unstake_time</span>: <span class="kt">u64</span><span class="p">,</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>On y stocke donc :</p>
<ul class="simple">
<li>son adresse Elrond.</li>
<li>le <strong>nonce</strong> de son NFT, qui représente le numéro du NFT dans la collection.</li>
<li>le moment où le NFT est locké, c-à-d quand il est envoyé dans la pool.</li>
<li>le moment où le NFT peut être déstaker via <strong>unstake_time</strong>.</li>
</ul>
<p>On passe ensuite à l'écriture du SC dans <strong>src/empty.rs</strong>.</p>
<p>On initialise les différents arguments de notre SC via la fonction <strong>init</strong>.</p>
<ul class="simple">
<li><em>nft_identifier</em> : l'identifiant de la collection NFT concernée.</li>
<li><em>minimum_staking_days</em> : le nombre de jours minimum avant de pouvoir déstaker son NFT.</li>
<li><em>rewards_token_id</em> : l'identifiant du token ESDT pour les récompenses.</li>
<li><em>rewards_token_amount_per_day</em> : le montant par jour des récompenses du staking.</li>
<li><em>rewards_token_total_supply</em> : le nombre total de tokens disponibles dans le SC.</li>
</ul>
<p>Et on initie quelques paramètres supplémentaires comme :</p>
<ul class="simple">
<li><em>staking_status</em> : permet de définir si la pool est démarrée où non.</li>
<li><em>staking_end_time</em> : permet de définir le moment où la pool sera fermée.</li>
<li><em>nbr_of_stakers</em> : permet de définir le nombre de stakers dans la pool.</li>
</ul>
<div class="highlight"><pre><span></span><span class="cp">#![no_std]</span><span class="w"></span>
<span class="n">elrond_wasm</span>::<span class="n">imports</span><span class="o">!</span><span class="p">();</span><span class="w"></span>
<span class="k">mod</span> <span class="nn">stake_info</span><span class="p">;</span><span class="w"></span>
<span class="k">use</span><span class="w"> </span><span class="n">stake_info</span>::<span class="n">StakeInfo</span><span class="p">;</span><span class="w"></span>
<span class="cp">#[elrond_wasm::contract]</span><span class="w"></span>
<span class="k">pub</span><span class="w"> </span><span class="k">trait</span><span class="w"> </span><span class="n">NftStaking</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="cp">#[init]</span><span class="w"></span>
<span class="w"> </span><span class="k">fn</span> <span class="nf">init</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">nft_identifier</span>: <span class="nc">TokenIdentifier</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">minimum_staking_days</span>: <span class="kt">u64</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">rewards_token_id</span>: <span class="nc">TokenIdentifier</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">rewards_token_amount_per_day</span>: <span class="nc">BigUint</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">rewards_token_total_supply</span>: <span class="nc">BigUint</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nft_identifier</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">nft_identifier</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">minimum_staking_days</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">minimum_staking_days</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_id</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">rewards_token_id</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_amount_per_day</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">rewards_token_amount_per_day</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_total_supply</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">rewards_token_total_supply</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// if staking status is empty, set it to false</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_status</span><span class="p">().</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_status</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">// if staking end time is empty, set it to 0</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_end_time</span><span class="p">().</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_end_time</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">// if nbr of stakers is empty, set it to 0</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Tu peux alors définir les <strong>storage_mapper</strong> et les <strong>view</strong> pour ces différents paramètres.
Le <strong>storage_mapper</strong> appelé <strong>staking_info</strong> va permettre de stoker un objet <strong>StakeInfo</strong>
par adresse Elrond via la définition <strong>SingleValueMapper<StakeInfo<Self::Api>></strong>.</p>
<div class="highlight"><pre><span></span><span class="cp">#[view(getNftIdentifier)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"nft_identifier"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">nft_identifier</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">TokenIdentifier</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getMinimumStakingDays)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"minimum_staking_days"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">minimum_staking_days</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="kt">u64</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getRewardsTokenId)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"rewards_token_id"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">rewards_token_id</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">TokenIdentifier</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getRewardsTokenAmountPerDay)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"rewards_token_amount_per_day"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">rewards_token_amount_per_day</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">BigUint</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getStakingInfo)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"staking_info"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">staking_info</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">address</span>: <span class="kp">&</span><span class="nc">ManagedAddress</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">StakeInfo</span><span class="o"><</span><span class="bp">Self</span>::<span class="n">Api</span><span class="o">>></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getStakingStatus)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"staking_status"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">staking_status</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="kt">bool</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getStakingEndTime)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"staking_end_time"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">staking_end_time</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="kt">u64</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getRewardsTokenTotalSupply)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"rewards_token_total_supply"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">rewards_token_total_supply</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">BigUint</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="cp">#[view(getNbrOfStakers)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"nbr_of_stakers"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">nbr_of_stakers</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="kt">u64</span><span class="o">></span><span class="p">;</span><span class="w"></span>
</pre></div>
<p>On passe ensuite à la fonction de staking. Celle-ci doit être <strong>payable</strong> car on y envoit son NFT.
Il faut vérifier le que le staking est démarré et que le NFT envoyé est bien dans la collection.
On vérifie aussi qu'un NFT n'est pas déjà locké par cet utilisateur.</p>
<p>On définit alors les différents paramètres de son <strong>StakeInfo</strong> que l'on sauvegarde dans la blockchain
et on incrémente le <strong>nbr_of_stakers</strong>. On ajoute également le moment où il pourra déstaker son NFT.</p>
<p>Ne pas oublier le <strong>Ok(())</strong> à la fin !</p>
<div class="highlight"><pre><span></span><span class="cp">#[payable(</span><span class="s">"*"</span><span class="cp">)]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">stake</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">payment</span>: <span class="nc">EsdtTokenPayment</span><span class="o"><</span><span class="bp">Self</span>::<span class="n">Api</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">call_value</span><span class="p">().</span><span class="n">payment</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">payment_token</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">payment</span><span class="p">.</span><span class="n">token_identifier</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">payment_nonce</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">payment</span><span class="p">.</span><span class="n">token_nonce</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">payment_amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">payment</span><span class="p">.</span><span class="n">amount</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_status</span><span class="p">().</span><span class="n">get</span><span class="p">(),</span><span class="w"> </span><span class="s">"The staking is stopped"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">payment_token</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nft_identifier</span><span class="p">().</span><span class="n">get</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="s">"Invalid nft identifier"</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="n">payment_nonce</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="s">"Invalid nft nonce"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="n">payment_amount</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s">"You can only send 1 nft"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">caller</span>: <span class="nc">ManagedAddress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="s">"You have already staked."</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">cur_time</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_block_timestamp</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">unstake_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cur_time</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">minimum_staking_days</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">86400</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">StakeInfo</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">address</span>: <span class="nc">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">nft_nonce</span>: <span class="nc">payment_nonce</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">lock_time</span>: <span class="nc">cur_time</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">unstake_time</span>: <span class="nc">unstake_time</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">())</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">stake_info</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Notre utilisateur peut désormais staker son NFT. Maintenant il faut qu'il puisse le déstaker.
On vérifie qu'il a bien un <strong>StakeInfo</strong> stocké dans la blockchain avec son adresse et qu'il a bien
dépassé le nombre minimum de jours de staking.</p>
<p>Si c'est le cas, on lui envoie son NFT via <strong>self.send().direct()</strong>, on supprime son entrée <strong>StakeInfo</strong>
et on décrémente le <strong>nbr_of_stakers</strong>.</p>
<div class="highlight"><pre><span></span><span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">unstake</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">caller</span>: <span class="nc">ManagedAddress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">cur_time</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_block_timestamp</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">unstake_time</span><span class="w"> </span><span class="o"><=</span><span class="w"> </span><span class="n">cur_time</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s">"You can't unlock staking nft yet."</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">nft_identifier</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nft_identifier</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">nft_nonce</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">nft_nonce</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">BigUint</span>::<span class="n">from</span><span class="p">(</span><span class="mi">1</span><span class="k">u32</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">send</span><span class="p">().</span><span class="n">direct</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">caller</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">nft_identifier</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">nft_nonce</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="n">amount</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s">b"unstake successful"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">clear</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">nbr_of_stakers</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Troisème grosse étape après le staking et le déstaking : la possibilté de réclamer ses récompenses.
On calcule ses récompenses en fonction du nombre de jours de staking et du moment où il a locké son NFT.
On vérifie également qu'il reste bien des tokens disponibles dans le SC.</p>
<p>Après avoir envoyé ses récompenses, on met à jour son <strong>StakeInfo</strong> en redéfinissant son <strong>lock time</strong>
à maintenant. Ce qui va permet de relancer le calcul des futurs récompenses à partir de là.</p>
<div class="highlight"><pre><span></span><span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">claim</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">caller</span>: <span class="nc">ManagedAddress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">cur_time</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_block_timestamp</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">rewards_token_total_supply</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_total_supply</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">nft_nonce</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">nft_nonce</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">unstake_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">unstake_time</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">reward_token_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_id</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="c1">// calculate rewards</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">from_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cur_time</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_status</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">from_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_end_time</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">staked_days</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="k">u64</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">from_time</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">lock_time</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">staked_days</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">from_time</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">lock_time</span><span class="p">)</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">86400</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">rewards_amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_amount_per_day</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">staked_days</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">// check the supply</span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">rewards_amount</span><span class="w"> </span><span class="o"><=</span><span class="w"> </span><span class="n">rewards_token_total_supply</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s">"You can't claim rewards more than total supply."</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// send rewards</span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">send</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">direct</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">reward_token_id</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">rewards_amount</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="p">[]);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// remove rewards amount from rewards_token_total_supply</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">rewards_token_total_supply</span><span class="w"> </span><span class="o">>=</span><span class="w"> </span><span class="n">rewards_amount</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_total_supply</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="p">(</span><span class="n">rewards_token_total_supply</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">rewards_amount</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_total_supply</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">BigUint</span>::<span class="n">from</span><span class="p">(</span><span class="mi">0</span><span class="k">u32</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="c1">// update staking_info</span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">clear</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">StakeInfo</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">address</span>: <span class="nc">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">nft_nonce</span>: <span class="nc">nft_nonce</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">lock_time</span>: <span class="nc">from_time</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">unstake_time</span>: <span class="nc">unstake_time</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">())</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">stake_info</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Le plus gros est fait ! On ajoute quelques fonctions d'administration supplémentaires pour le possesseur du
SC, à savoir :</p>
<ul class="simple">
<li><em>set_rewards_token_total_supply</em> : pour définir le nombre total de tokens disponibles dans la pool pour récompenser
les utilisateurs.</li>
<li><em>set_rewards_token_amount_per_day</em> : la possibilité de modifier le nombre de récompenses journalières.</li>
<li><em>withdraw</em> : la possibilité de récupérer tous les tokens du SC, au cas où.</li>
<li><em>stop_staking</em> : la possibilité de stopper le staking.</li>
<li><em>restart_staking</em> : la possibilité de relancer le staking.</li>
</ul>
<div class="highlight"><pre><span></span><span class="cp">#[only_owner]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">set_rewards_token_total_supply</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">total_supply</span>: <span class="nc">BigUint</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_total_supply</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">total_supply</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="c1">// set rewards_token_amount_per_day</span>
<span class="cp">#[only_owner]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">set_rewards_token_amount_per_day</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">amount</span>: <span class="nc">BigUint</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_amount_per_day</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">amount</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="cp">#[only_owner]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">withdraw</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">amount</span>: <span class="nc">BigUint</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">caller</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">token_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_id</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">send</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">direct</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">token_id</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">amount</span><span class="p">,</span><span class="w"> </span><span class="s">b"withdraw successful"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="cp">#[only_owner]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">restart_staking</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_end_time</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_status</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="cp">#[only_owner]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">stop_staking</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">cur_time</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_block_timestamp</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_end_time</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="n">cur_time</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_status</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Enfin, tu peux ajouter quelques vues qui seront utiles pour l'affichage dans ta <strong>dapp</strong>.</p>
<ul class="simple">
<li><em>get_current_rewards</em> : pour afficher le montant des récompenses récupérables actuellement.</li>
<li><em>get_nft_nonce</em> : pour afficher le numéro du NFT que l'utilisateur a locké.</li>
<li><em>get_lock_time</em> : pour afficher le moment où l'utilisateur a locké son NFT.</li>
<li><em>get_unstake_time</em> : pour afficher le moment où l'utilisateur peut déstaker son NFT.</li>
</ul>
<div class="highlight"><pre><span></span><span class="cp">#[view(getCurrentRewards)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">get_current_rewards</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">address</span>: <span class="kp">&</span><span class="nc">ManagedAddress</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">BigUint</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">cur_time</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_block_timestamp</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="c1">// calculate rewards</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">from_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">cur_time</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_status</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">from_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_end_time</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">staked_days</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="k">u64</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">from_time</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">lock_time</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">staked_days</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">from_time</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">lock_time</span><span class="p">)</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">86400</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">rewards_amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">rewards_token_amount_per_day</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">staked_days</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">rewards_amount</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="cp">#[view(getNftNonce)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">get_nft_nonce</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">address</span>: <span class="kp">&</span><span class="nc">ManagedAddress</span><span class="p">)</span><span class="w"> </span>-> <span class="kt">u64</span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">nft_nonce</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">nft_nonce</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">nft_nonce</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="cp">#[view(getLockTime)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">get_lock_time</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">address</span>: <span class="kp">&</span><span class="nc">ManagedAddress</span><span class="p">)</span><span class="w"> </span>-> <span class="kt">u64</span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">lock_time</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">lock_time</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">lock_time</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="cp">#[view(getUnstakeTime)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">get_unstake_time</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">address</span>: <span class="kp">&</span><span class="nc">ManagedAddress</span><span class="p">)</span><span class="w"> </span>-> <span class="kt">u64</span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"You didn't stake!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">stake_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">staking_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">unstake_time</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="n">stake_info</span><span class="p">.</span><span class="n">unstake_time</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">unstake_time</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Tu utilises alors à nouveau <strong>erdpy</strong> pour compiler ton SC et vérifier que tout se passe bien :</p>
<div class="highlight"><pre><span></span>erdpy contract build
</pre></div>
<p>Le code final est visible <a class="reference external" href="https://github.com/dotmobo/dbc-dashboard/blob/master/contract/nft_staking/src/empty.rs">ici</a>.</p>
</div>
<div class="section" id="deploiement">
<h2>Déploiement</h2>
<p>Pour déployer, il te faut un fichier <strong>erdpy.json</strong> à la racine du projet. Comme pour l'épisode précédent,
on va déployer sur <strong>devnet</strong> et on suppose que le <strong>pem</strong> de ton <strong>wallet</strong> est dans <strong>../../wallet/wallet-owner.pem</strong>.</p>
<p>Concernant les arguments ici :</p>
<ul class="simple">
<li><em>BACKGROUND-35c061</em> : l'id de la collection NFT concernée</li>
<li><em>10</em> : nombre de jours minimum de staking</li>
<li><em>DEADBROS-fa8f0f</em> : l'id de l'ESDT utilisé pour les récompenses</li>
<li><em>100000000000000000000</em> : le montant journalier des récompenses. C'est sur 18 décimals, donc en vrai on a 100 $DEAD de récompenses.</li>
<li><em>6000000000000000000000000</em> : le nombre total de tokens disponibles dans le SC. 6 millions donc.</li>
</ul>
<div class="highlight"><pre><span></span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"default"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"proxy"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://devnet-api.elrond.com"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"chainID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"D"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nt">"contract"</span><span class="p">:{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"deploy"</span><span class="p">:{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"verbose"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"bytecode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"output/nft_staking.wasm"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"recall-nonce"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"metadata-payable"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"pem"</span><span class="p">:</span><span class="w"> </span><span class="s2">"../../wallet/wallet-owner.pem"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"gas-limit"</span><span class="p">:</span><span class="w"> </span><span class="mi">59999999</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"arguments"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"str:BACKGROUND-35c061"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"10"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"str:DEADBROS-fa8f0f"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"100000000000000000000"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"6000000000000000000000000"</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">"send"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"outfile"</span><span class="p">:</span><span class="w"> </span><span class="s2">"deploy-testnet.interaction.json"</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nt">"upgrade"</span><span class="p">:{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"verbose"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"bytecode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"output/nft_staking.wasm"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"recall-nonce"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"metadata-payable"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"pem"</span><span class="p">:</span><span class="w"> </span><span class="s2">"../../wallet/wallet-owner.pem"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"gas-limit"</span><span class="p">:</span><span class="w"> </span><span class="mi">59999999</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"arguments"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"str:BACKGROUND-35c061"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"10"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"str:DEADBROS-fa8f0f"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"100000000000000000000"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"6000000000000000000000000"</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">"send"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"outfile"</span><span class="p">:</span><span class="w"> </span><span class="s2">"deploy-testnet.interaction.json"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Le SC doit être payable via <strong>"metadata-payable": true</strong>, car ça va nous permettre d'y envoyer directement
le nombre total de tokens pour les récompenses via une transaction standard avec Maiar.</p>
<p>Tu peux alors déployer et tester les transactions avec <strong>erdpy</strong>.</p>
<div class="highlight"><pre><span></span>erdpy contract deploy
erdpy tx new --help
</pre></div>
<p>Il ne te reste plus qu'à coder <a class="reference external" href="https://github.com/dotmobo/dbc-dashboard/blob/master/dapp/src/components/NftStaking/index.tsx">une interface frontend pour le SC</a>.</p>
<p>Bon courage ! Et n'hésite pas à améliorer tout ça et à nous faire un retour !</p>
</div>
Smart Contract Elrond en Rust : DAO vote2022-07-06T00:00:00+02:002022-07-06T00:00:00+02:00Morgantag:dotmobo.github.io,2022-07-06:/elrond-sc-rust-dao-vote.html<p class="first last">Smart Contract Elrond en Rust : DAO vote</p>
<img alt="Elrond" class="align-right" src="./images/elrond.png" />
<p>Allez, c'est parti pour une série d'articles concernant l'écriture de Smart Contract <a class="reference external" href="https://elrond.com/">Elrond</a>
en <a class="reference external" href="https://rust-lang.org/">Rust</a>.
Etant dans l'équipe d'un projet NFT sur Elrond, j'ai pu explorer certains facettes de cette blockchain.</p>
<div class="section" id="contexte">
<h2>Contexte</h2>
<p>On va commencer par écrire un SC en Rust pour implémenter le système de vote d'une <a class="reference external" href="https://fr.wikipedia.org/wiki/Organisation_autonome_d%C3%A9centralis%C3%A9e">DAO</a>.
Imaginons que la DAO de notre projet NFT a son propre token (ici $DEAD). Et que nous souhaitons mettre en place
un système de vote où les membres peuvent utiliser des $DEAD pour voter.</p>
<p>Ici, le nombre de $DEAD accumulés équivaut à une puissance de vote. Les membres qui ont plus de tokens
ont donc plus de pouvoir dans la DAO.</p>
<p>On aura donc une question stockée dans le SC et la possibilité de voter oui ou non avec des $DEAD.
A la fin du vote, les membres doivent pouvoir récupérer leurs tokens.</p>
</div>
<div class="section" id="tools">
<h2>Tools</h2>
<p>Tu vas avoir besoin de Python 3, de Rust et de Erdpy.</p>
<div class="highlight"><pre><span></span>sudo apt install libncurses5 python3
wget -O erdpy-up.py https://raw.githubusercontent.com/ElrondNetwork/elrond-sdk-erdpy/master/erdpy-up.py
python3.8 erdpy-up.py
curl --proto <span class="s1">'=https'</span> --tlsv1.2 -sSf https://sh.rustup.rs <span class="p">|</span> sh
</pre></div>
<p>Prends ton éditeur préféré et c'est parti !</p>
</div>
<div class="section" id="smart-contract">
<h2>Smart Contract</h2>
<p>On va utiliser un template vide à l'aide de <strong>erdpy</strong> pour créer un SC. Tu peux trouver des informations complémentaire
dans la <a class="reference external" href="https://docs.elrond.com/sdk-and-tools/erdpy/erdpy/">documentation officielle</a>, notamment pour te créer le fichier
<strong>pem</strong> de ton <strong>wallet</strong> nécessaire au déploiement en production.</p>
<div class="highlight"><pre><span></span>erdpy contract new vote --template empty
</pre></div>
<p>Pour faire ça proprement, on va créer un fichier <strong>vote_info.rs</strong> dans <strong>src</strong> qui va contenir
la structure de notre vote. On va y stocker l'adresse Elrond du votant ainsi qui le nombre de $DEAD
qu'il a utilisé pour voter.</p>
<div class="highlight"><pre><span></span><span class="n">elrond_wasm</span>::<span class="n">imports</span><span class="o">!</span><span class="p">();</span><span class="w"></span>
<span class="n">elrond_wasm</span>::<span class="n">derive_imports</span><span class="o">!</span><span class="p">();</span><span class="w"></span>
<span class="cp">#[derive(TypeAbi, TopEncode, TopDecode, PartialEq, Debug)]</span><span class="w"></span>
<span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">VoteInfo</span><span class="o"><</span><span class="n">M</span>: <span class="nc">ManagedTypeApi</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">pub</span><span class="w"> </span><span class="n">address</span>: <span class="nc">ManagedAddress</span><span class="o"><</span><span class="n">M</span><span class="o">></span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">pub</span><span class="w"> </span><span class="n">amount</span>: <span class="nc">BigUint</span><span class="o"><</span><span class="n">M</span><span class="o">></span><span class="p">,</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Le fichier qui va principalement nous intéresser est <strong>src/empty.rs</strong>.</p>
<p>A chaque déploiement ou mise à jour du smart contract, la fonction <strong>init</strong> est appelée.
Il faut donc faire attention à ne pas écraser des valeurs !</p>
<p>On va prendre en paramètre la question et l'identifiant du token que l'on souhaite utiliser pour voter.
Et on initialise à 0 le nombre total de vote "oui" et de vote "non", et on démarre le vote.</p>
<div class="highlight"><pre><span></span><span class="cp">#![no_std]</span><span class="w"></span>
<span class="n">elrond_wasm</span>::<span class="n">imports</span><span class="o">!</span><span class="p">();</span><span class="w"></span>
<span class="k">mod</span> <span class="nn">vote_info</span><span class="p">;</span><span class="w"></span>
<span class="k">use</span><span class="w"> </span><span class="n">vote_info</span>::<span class="n">VoteInfo</span><span class="p">;</span><span class="w"></span>
<span class="k">use</span><span class="w"> </span><span class="n">elrond_wasm</span>::<span class="n">types</span>::<span class="n">heap</span>::<span class="n">BoxedBytes</span><span class="p">;</span><span class="w"></span>
<span class="cp">#[elrond_wasm::contract]</span><span class="w"></span>
<span class="k">pub</span><span class="w"> </span><span class="k">trait</span><span class="w"> </span><span class="n">Vote</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// La fonction d'initialisation au déployement du contrat</span>
<span class="w"> </span><span class="c1">// On définit la question et les réponses possibles</span>
<span class="w"> </span><span class="cp">#[init]</span><span class="w"></span>
<span class="w"> </span><span class="k">fn</span> <span class="nf">init</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">question</span>: <span class="nc">BoxedBytes</span><span class="p">,</span><span class="w"> </span><span class="n">token_id</span>: <span class="nc">TokenIdentifier</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">question</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">question</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">token_id</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">token_id</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">yes</span><span class="p">().</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">yes</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="n">BigUint</span>::<span class="n">from</span><span class="p">(</span><span class="mi">0</span><span class="k">u32</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">no</span><span class="p">().</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">no</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="n">BigUint</span>::<span class="n">from</span><span class="p">(</span><span class="mi">0</span><span class="k">u32</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">in_progress</span><span class="p">().</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">in_progress</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="mi">1</span><span class="k">u32</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>En bas de notre <strong>trait</strong>, on va définir l'ensemble des valeurs que l'on souhaite stocker dans le SC.
C'est directement stocké dans la blockchain via <strong>storage_mapper</strong> et on peut visualiser les données depuis
une dapp par exemple en appelant les méthodes de <strong>view</strong>.</p>
<div class="highlight"><pre><span></span><span class="c1">// La question</span>
<span class="cp">#[view(getQuestion)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"question"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">question</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">BoxedBytes</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="c1">// Le nombre de votes pour la réponse "oui"</span>
<span class="cp">#[view(getYes)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"yes"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">yes</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">BigUint</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="c1">// Le nombre de votes pour la réponse "non"</span>
<span class="cp">#[view(getNo)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"no"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">no</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">BigUint</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="c1">// Le token utilisé pour les votes</span>
<span class="cp">#[view(getTokenId)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"token_id"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">token_id</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">TokenIdentifier</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="c1">// Si le vote est terminé</span>
<span class="cp">#[view(getInProgress)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"in_progress"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">in_progress</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="kt">u32</span><span class="o">></span><span class="p">;</span><span class="w"></span>
<span class="c1">// L'information du votant</span>
<span class="cp">#[view(getVoteInfo)]</span><span class="w"></span>
<span class="cp">#[storage_mapper(</span><span class="s">"vote_info"</span><span class="cp">)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">vote_info</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">address</span>: <span class="kp">&</span><span class="nc">ManagedAddress</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SingleValueMapper</span><span class="o"><</span><span class="n">VoteInfo</span><span class="o"><</span><span class="bp">Self</span>::<span class="n">Api</span><span class="o">>></span><span class="p">;</span><span class="w"></span>
</pre></div>
<p>Ensuite, on va créer nos deux fonctions <strong>vote_yes</strong> et <strong>vote_no</strong>.
Il y a sûrement moyen de mutualiser ces deux fonctions, mais j'ai fait au plus rapide.</p>
<p>Il faut mettre quelques garde-fous, notamment pour vérifier que le type de token envoyé est le bon et
que le vote est toujours en cours.</p>
<p>Cette fonction doit donc être <strong>payable</strong>. Son fonctionnement est tout simple. On incrémente le nombre total de "oui" ou de "non", et on
ajoute ou met à jour l'information du votant.</p>
<p>Le <strong>Ok(())</strong> à la fin est important, car il permet de confirmer que la transaction s'est bien effectuée.</p>
<div class="highlight"><pre><span></span><span class="cp">#[payable(</span><span class="s">"*"</span><span class="cp">)]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">vote_yes</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="cp">#[payment_token]</span><span class="w"> </span><span class="n">payment_token</span>: <span class="nc">TokenIdentifier</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="cp">#[payment_amount]</span><span class="w"> </span><span class="n">payment_amount</span>: <span class="nc">BigUint</span><span class="p">,</span><span class="w"></span>
<span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">payment_token</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">token_id</span><span class="p">().</span><span class="n">get</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="s">"Invalid payment token"</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">in_progress</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">1</span><span class="k">u32</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s">"the vote is over"</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">yes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">yes</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">yes</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">yes</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">&</span><span class="n">payment_amount</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// save info about the voter</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">caller</span>: <span class="nc">ManagedAddress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="c1">// if vote info exists, update it</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">new_vote_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">VoteInfo</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">address</span>: <span class="nc">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">amount</span>: <span class="nc">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">get</span><span class="p">().</span><span class="n">amount</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">&</span><span class="n">payment_amount</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">clear</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">new_vote_info</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// else create a new vote info</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">new_vote_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">VoteInfo</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">address</span>: <span class="nc">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">amount</span>: <span class="nc">payment_amount</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">new_vote_info</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="c1">// On peut voter non en envoyant autant de tokens que souhaité</span>
<span class="cp">#[payable(</span><span class="s">"*"</span><span class="cp">)]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">vote_no</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="cp">#[payment_token]</span><span class="w"> </span><span class="n">payment_token</span>: <span class="nc">TokenIdentifier</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="cp">#[payment_amount]</span><span class="w"> </span><span class="n">payment_amount</span>: <span class="nc">BigUint</span><span class="p">,</span><span class="w"></span>
<span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">payment_token</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">token_id</span><span class="p">().</span><span class="n">get</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="s">"Invalid payment token"</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">in_progress</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">1</span><span class="k">u32</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s">"the vote is over"</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">no</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">no</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">no</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">no</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">&</span><span class="n">payment_amount</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// save info about the voter</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">caller</span>: <span class="nc">ManagedAddress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="c1">// if vote info exists, update it</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">is_empty</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">new_vote_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">VoteInfo</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">address</span>: <span class="nc">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">amount</span>: <span class="nc">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">get</span><span class="p">().</span><span class="n">amount</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">&</span><span class="n">payment_amount</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">clear</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">new_vote_info</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="c1">// else create a new vote info</span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">new_vote_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">VoteInfo</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">address</span>: <span class="nc">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">(),</span><span class="w"></span>
<span class="w"> </span><span class="n">amount</span>: <span class="nc">payment_amount</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">};</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">new_vote_info</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>A la fin du vote, le votant doit pouvoir récupérer ses tokens. On créé donc une fonction qui sera appelée
par le votant via un bouton dans la <strong>dapp</strong>. On effectue donc une transaction pour lui envoyer le montant total
de ses tokens stockés dans le SC via la fonction <strong>self.send().direct()</strong>.</p>
<div class="highlight"><pre><span></span><span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">withdraw_my_amount</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">in_progress</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="k">u32</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s">"the vote is not over"</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">caller</span>: <span class="nc">ManagedAddress</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"Nothing to withdraw!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">vote_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">my_amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">vote_info</span><span class="p">.</span><span class="n">amount</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">token_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">token_id</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">send</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">direct</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">token_id</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">my_amount</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="p">[]);</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">).</span><span class="n">clear</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>On donne également la possibilité à l'administrateur du smart contract de retirer tous les tokens, au cas où.
On additionne ici le total des "oui" et des "non" et on envoie le tout à l'administrateur.</p>
<div class="highlight"><pre><span></span><span class="cp">#[only_owner]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">withdraw</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">in_progress</span><span class="p">().</span><span class="n">get</span><span class="p">()</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="k">u32</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s">"the vote is not over"</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">caller</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">blockchain</span><span class="p">().</span><span class="n">get_caller</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">yes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">yes</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">no</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">no</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">token_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">token_id</span><span class="p">().</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&</span><span class="n">yes</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="o">&</span><span class="n">no</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">send</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">direct</span><span class="p">(</span><span class="o">&</span><span class="n">caller</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">token_id</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="o">&</span><span class="n">amount</span><span class="p">,</span><span class="w"> </span><span class="s">b"withdraw successful"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Enfin, on ajoute quelques petites fonctions pratiques.</p>
<p>Par exemple, pour stopper le vote :</p>
<div class="highlight"><pre><span></span><span class="cp">#[only_owner]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">finish_vote</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">in_progress</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="mi">0</span><span class="k">u32</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Ou pour changer la question :</p>
<div class="highlight"><pre><span></span><span class="cp">#[only_owner]</span><span class="w"></span>
<span class="cp">#[endpoint]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">change_question</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">question</span>: <span class="nc">BoxedBytes</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">SCResult</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">question</span><span class="p">().</span><span class="n">set</span><span class="p">(</span><span class="o">&</span><span class="n">question</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Attention à bien utiliser <strong>only_owner</strong>, sinon n'importe qui pourra appeler ces fonctions !</p>
<p>Enfin, pour récupérer le nombre total de tokens utilisés par le votant :</p>
<div class="highlight"><pre><span></span><span class="cp">#[view(getMyAmount)]</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">get_my_amount</span><span class="p">(</span><span class="o">&</span><span class="bp">self</span><span class="p">,</span><span class="w"> </span><span class="n">address</span>: <span class="kp">&</span><span class="nc">ManagedAddress</span><span class="p">)</span><span class="w"> </span>-> <span class="nc">BigUint</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">require</span><span class="o">!</span><span class="p">(</span><span class="o">!</span><span class="bp">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">is_empty</span><span class="p">(),</span><span class="w"> </span><span class="s">"Nothing to withdraw!"</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">vote_info</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="n">vote_info</span><span class="p">(</span><span class="o">&</span><span class="n">address</span><span class="p">).</span><span class="n">get</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">vote_info</span><span class="p">.</span><span class="n">amount</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">amount</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Et voilà ! Tu peux jeter un oeil au résultat final sur <a class="reference external" href="https://github.com/dotmobo/dbc-dashboard/blob/master/contract/vote/src/empty.rs">mon github</a>.</p>
<p>Pour tester si tout fonctionne, tu utilise <strong>erdpy</strong> pour compiler ton SC :</p>
<div class="highlight"><pre><span></span>erdpy contract build
</pre></div>
<p>Et pour déployer, il te faut créer à la racine du projet un fichier <strong>erdpy.json</strong> avec les informations
nécessaires au déploiement.
Ici, on déploie sur <strong>devnet</strong>.
Tu pourras trouver dans <strong>arguments</strong> les 2 arguments nécessaires au <strong>init</strong> du SC.
<strong>DEADBROS-fa8f0f</strong> est l'id du token $DEAD sur devnet.</p>
<div class="highlight"><pre><span></span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"default"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"proxy"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://devnet-api.elrond.com"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"chainID"</span><span class="p">:</span><span class="w"> </span><span class="s2">"D"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nt">"contract"</span><span class="p">:{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"deploy"</span><span class="p">:{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"verbose"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"bytecode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"output/vote.wasm"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"recall-nonce"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"pem"</span><span class="p">:</span><span class="w"> </span><span class="s2">"../../wallet/wallet-owner.pem"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"gas-limit"</span><span class="p">:</span><span class="w"> </span><span class="mi">59999999</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"arguments"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"str:Do you approve of the following plan regarding Dawn DeadBrothers funds: 35% EGLD staking, 35% LKMEX farming, 5% foundation, 5% charity, 10% marketing, 10% team ?"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"str:DEADBROS-fa8f0f"</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">"send"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"outfile"</span><span class="p">:</span><span class="w"> </span><span class="s2">"deploy-testnet.interaction.json"</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nt">"upgrade"</span><span class="p">:{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"verbose"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"bytecode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"output/vote.wasm"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"recall-nonce"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"pem"</span><span class="p">:</span><span class="w"> </span><span class="s2">"../../wallet/wallet-owner.pem"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"gas-limit"</span><span class="p">:</span><span class="w"> </span><span class="mi">59999999</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"arguments"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="s2">"str:Do you approve of the following plan regarding Dawn DeadBrothers funds: 35% EGLD staking, 35% LKMEX farming, 5% foundation, 5% charity, 10% marketing, 10% team ?"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"str:DEADBROS-fa8f0f"</span><span class="w"></span>
<span class="w"> </span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">"send"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"outfile"</span><span class="p">:</span><span class="w"> </span><span class="s2">"deploy-testnet.interaction.json"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>On imagine ici que tu as le <strong>pem</strong> de ton <strong>wallet</strong> dans <strong>../../wallet/wallet-owner.pem</strong>.
N'oublie pas d'utiliser un <strong>faucet</strong> pour récupérer des EGLD de tests, car le déploiement a un coût !</p>
<p>Tu déploie le tout et tu croises les doigts !</p>
<div class="highlight"><pre><span></span>erdpy contract deploy
</pre></div>
<p>Tu peux désormais utiliser <strong>erdpy</strong> pour effectuer des transactions et tester les différentes fonctions de ton SC.</p>
<div class="highlight"><pre><span></span>erdpy tx new --help
</pre></div>
<p>Ou alors tu peux directement passer à l'écriture de ton <a class="reference external" href="https://github.com/ElrondNetwork/dapp-template">application frontend</a> !</p>
<p>Avec une <a class="reference external" href="https://github.com/dotmobo/dbc-dashboard/blob/master/dapp/src/pages/Dao/Components/Vote.tsx">dapp associée</a>, ça peut ressembler à ça :</p>
<img alt="Dao_vote" src="./images/dao_vote.png" />
<p>Have fun !</p>
</div>
Mon environnement python en 20222022-01-28T00:00:00+01:002022-01-28T00:00:00+01:00Morgantag:dotmobo.github.io,2022-01-28:/environnement-python-2022.html<p class="first last">Mon environnement python en 2022</p>
<img alt="Python" class="align-right" src="./images/python.png" />
<p>Pour cette nouvelle année 2022, on va voir ensemble les quelques librairies plutôt cools pour
se faire un environnement de dev sympa et efficace en python ! On teste avec <strong>python 3.9</strong> car il y a encore des soucis de compatibilité
pour certaines librairies avec python 3.10.</p>
<div class="section" id="versions-de-python">
<h2>Versions de python</h2>
<p>Pour gérer les différentes versions de python sur ta machine, il est plutôt pratique d'utiliser <a class="reference external" href="https://github.com/pyenv/pyenv">pyenv</a>.
Pour les devs JS, c'est un équivalent à <a class="reference external" href="https://github.com/nvm-sh/nvm">nvm</a> et à <a class="reference external" href="https://github.com/tj/n">n</a> mais pour python.</p>
<p>Pour l'installer et l'utiliser rien de plus simple :</p>
<div class="highlight"><pre><span></span>sudo apt-get install aria2 build-essential curl git libbz2-dev libffi-dev liblzma-dev <span class="se">\</span>
libncurses5-dev libncursesw5-dev libreadline-dev libsqlite3-dev libssl-dev llvm make tk-dev wget xz-utils zlib1g-dev
curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer <span class="p">|</span> bash
<span class="nb">export</span> <span class="nv">PYENV_ROOT</span><span class="o">=</span><span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/.pyenv"</span>
<span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$PYENV_ROOT</span><span class="s2">/bin:</span><span class="nv">$PATH</span><span class="s2">"</span>
<span class="nb">eval</span> <span class="s2">"</span><span class="k">$(</span>pyenv init -<span class="k">)</span><span class="s2">"</span>
pyenv install <span class="m">3</span>.9.10
</pre></div>
<p>Après, si tu n'as pas besoin de gérer plein de versions différentes régulièrement, tu peux te cantonner à utiliser les packages de ta distro.</p>
</div>
<div class="section" id="environnement-virtuel-et-packaging">
<h2>Environnement virtuel et packaging</h2>
<p>Pour ma part, fini les <a class="reference external" href="https://github.com/pypa/virtualenv">virtualenv</a>, <a class="reference external" href="https://virtualenvwrapper.readthedocs.io/en/latest/">virtualenvwrapper</a>
et <a class="reference external" href="https://docs.python.org/fr/3/distutils/setupscript.html">setup.py</a>, et place
à <a class="reference external" href="https://python-poetry.org/">poetry</a> et au fichier <a class="reference external" href="https://python-poetry.org/docs/pyproject/">pyproject.toml</a> !</p>
<p>La communauté python peine depuis un moment à avoir de bons outils pour gérer le packaging des applications et <strong>poetry</strong> vient apporter une solution à
tout ça. Certains diront que sa non-compatibilité avec les fichiers <strong>setup.py</strong> est le mal absolu car non-standard, ce qui n'est pas complètement faux ...
Mais cet outil est tellement pratique que j'en ai fait fi.</p>
<p>On l'installe et on initie un nouveau projet avec un <strong>venv</strong> encapsulé automatiquement :</p>
<div class="highlight"><pre><span></span>curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py <span class="p">|</span> python -
poetry new --src myapp
<span class="nb">cd</span> myapp
poetry add black isort
<span class="nb">echo</span> <span class="s1">'print("Hello World!")'</span> > src/main.py
poetry run python src/main.py
poetry build
poetry publish
</pre></div>
<p>Ici on a ajouté les librairies black et isort dans les dépendances du projet et on a exécuté notre script python "Hello World!".
Ensuite, on a buildé un <strong>sdist</strong> et un <strong>wheel</strong> qu'on a déployé sur PyPI.</p>
<p>Et voici aperçu du fameux fichier <strong>pyproject.toml</strong> généré :</p>
<div class="highlight"><pre><span></span><span class="o">[</span>tool.poetry<span class="o">]</span>
<span class="nv">name</span> <span class="o">=</span> <span class="s2">"myapp"</span>
<span class="nv">version</span> <span class="o">=</span> <span class="s2">"0.1.0"</span>
<span class="nv">description</span> <span class="o">=</span> <span class="s2">""</span>
<span class="nv">authors</span> <span class="o">=</span> <span class="o">[</span><span class="s2">"mobo"</span><span class="o">]</span>
<span class="o">[</span>tool.poetry.dependencies<span class="o">]</span>
<span class="nv">python</span> <span class="o">=</span> <span class="s2">"^3.9"</span>
<span class="nv">black</span> <span class="o">=</span> <span class="s2">"^21.12b0"</span>
<span class="nv">isort</span> <span class="o">=</span> <span class="s2">"^5.10.1"</span>
<span class="o">[</span>tool.poetry.dev-dependencies<span class="o">]</span>
<span class="nv">pytest</span> <span class="o">=</span> <span class="s2">"^5.2"</span>
<span class="o">[</span>build-system<span class="o">]</span>
<span class="nv">requires</span> <span class="o">=</span> <span class="o">[</span><span class="s2">"poetry-core>=1.0.0"</span><span class="o">]</span>
build-backend <span class="o">=</span> <span class="s2">"poetry.core.masonry.api"</span>
</pre></div>
<p>Quel gain de temps énorme !</p>
</div>
<div class="section" id="formattage-du-code">
<h2>Formattage du code</h2>
<p><a class="reference external" href="https://github.com/psf/black">Black</a> domine désormais de très loin tous les linters et autres formatteurs de code python. Plus besoin de
cumuler et de configurer une tonne d'outils comme pep8, flake8, pylint, pyflakes, pychecker.
On dégage tout ça et on utilise que <strong>black</strong>. Il va se charger de formatter correctement ton code, point barre. C'est l'équivalent du gofmt de golang.</p>
<p>Est-ce que le rendu est parfait ? Non. Est-ce qu'il est suffisamment bon pour qu'on arrête de se poser la question de comment formater notre code ?
Oui, mille fois oui.</p>
<p>On l'installe et on l'exécute sur notre projet et c'est tout. Pour automatiser tout ça, tu peux même le mettre sur un git hook, au commit par exemple.</p>
<div class="highlight"><pre><span></span>poetry add black
poetry run black src/
</pre></div>
<p>Si tu es quelqu'un de tatillon qui aime avoir ses importations bien ordonnées, tu peux utiliser <a class="reference external" href="https://github.com/PyCQA/isort">isort</a>.
Comme pour black, tu l'installes et tu le lances sur ton projet et c'est réglé :</p>
<div class="highlight"><pre><span></span>poetry add isort
poetry run isort src/
</pre></div>
</div>
<div class="section" id="typage-statique">
<h2>Typage statique</h2>
<p>Suite au succès de <a class="reference external" href="https://www.typescriptlang.org/">typescript</a> pour le typage statique de javascript, une sorte d'équivalent a vu le
jour pour python sous la forme d'une librairie optionnelle appelée <a class="reference external" href="https://github.com/python/mypy">mypy</a>.
Cette librarie permet d'annoter notre code pour expliciter les types de nos arguments, fonctions, classes, etc ...</p>
<p>Tu peux écrire ce script par exemple :</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">age</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">"Hello </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">, you are </span><span class="si">{</span><span class="n">age</span><span class="si">}</span><span class="s2"> years old."</span>
<span class="nb">print</span><span class="p">(</span><span class="n">hello</span><span class="p">(</span><span class="s2">"Jean"</span><span class="p">,</span> <span class="mi">18</span><span class="p">))</span>
</pre></div>
<p>Et tu verifies le typage à l'aide de la commande suivante :</p>
<div class="highlight"><pre><span></span>poetry add mypy
poetry run mypy src/
</pre></div>
</div>
<div class="section" id="tests-unitaires">
<h2>Tests unitaires</h2>
<p>Rien de neuf sous le soleil, on utilise toujours <a class="reference external" href="https://docs.pytest.org/en/6.2.x/">pytest</a> qui est installé par defaut avec poetry :</p>
<div class="highlight"><pre><span></span>poetry run pytest
</pre></div>
<p>Il est même possible d'utiliser <a class="reference external" href="https://python-poetry.org/docs/faq/#is-tox-supported">tox avec poetry</a> pour exécuter les tests sous
plusieurs environnements avec un <strong>tox.ini</strong> ressemblant à ça :</p>
<div class="highlight"><pre><span></span><span class="o">[</span>tox<span class="o">]</span>
<span class="nv">isolated_build</span> <span class="o">=</span> <span class="nb">true</span>
<span class="nv">envlist</span> <span class="o">=</span> py27, py36
<span class="o">[</span>testenv<span class="o">]</span>
<span class="nv">whitelist_externals</span> <span class="o">=</span> poetry
<span class="nv">commands</span> <span class="o">=</span>
poetry install -v
poetry run pytest tests/
</pre></div>
</div>
<div class="section" id="developpement-web">
<h2>Développement web</h2>
<p>Rien de révolutionnaire ici non plus. Dans 80% des cas, pour les applications moyennes et complexes, <a class="reference external" href="https://www.django-rest-framework.org/">django-rest-framework</a>
couplé au framework front-end de ton choix fera le café.</p>
<p>Pour les 20% de cas d'applications très simples, je reste sur le framework <a class="reference external" href="https://bottlepy.org/">bottle</a> et l'orm <a class="reference external" href="https://github.com/coleifer/peewee">peewee</a>,
qui ne m'ont jamais fait défaut.</p>
<p>Je trouve que le combo <a class="reference external" href="https://flask.palletsprojects.com/">flask</a> + <a class="reference external" href="https://www.sqlalchemy.org/">sqlalchemy</a> est une espèce
d'entre-deux qui ne correspond à aucun de mes cas d'usage.</p>
<p>Bon dev à tous !</p>
</div>
Pico-8, mon coup de coeur !2021-03-23T00:00:00+01:002021-03-23T00:00:00+01:00Morgantag:dotmobo.github.io,2021-03-23:/pico8.html<p class="first last">Pico-8, mon coup de coeur !</p>
<img alt="Pico-8" class="align-right" src="./images/pico8.png" />
<p><a class="reference external" href="https://www.lexaloffle.com/pico-8.php">Pico-8</a> est ce qu'on appelle une <strong>Fantasy Console</strong>, c'est-à-dire qu'il s'agit d'une console virtuelle 8 bits
imaginaire. C'est un peu comme une cousine de la Game Boy n'ayant jamais existée. Et comme toutes les consoles, elle a des contraintes et des limitations.
Sa résolution est fixée à 128x128 pixels, le nombre de couleurs limité à 16 et la taille de la cartouche contenant le code du jeu ne peut pas dépasser 32 kilo-octets.</p>
<p>Mais pourquoi avoir mis ces contraintes ? N'est-ce pas gênant ? Et bien, c'est là qu'opère toute la magie de cette console.</p>
<p><strong>Ses limitations stimulent la créativité !</strong></p>
<ul class="simple">
<li>La résolution et la taille de la carte est fixe, ce qui permet d'avoir une vision globale du nombre de niveaux que notre jeu sera capable de supporter.</li>
<li>La limitation de la taille de la cartouche nous force à écrire du code simple et efficace.</li>
<li>Le style graphique sera forcément du pixel art.</li>
<li>On est obligé de piocher dans les couleurs proposées, donc pas besoin de réfléchir à des palettes de couleurs.</li>
<li>Les sons utilisables sont également proposés, à l'instar des couleurs.</li>
</ul>
<p>Du coup, le fait d'avoir un cadre imposé permet d'éviter de se poser trop de question sur notre jeu.</p>
<p><strong>Tous les outils sont intégrés !</strong></p>
<p>En effet, <em>Pico-8</em> propose un éditeur de code, un éditeur de son, un éditeur de musique, un éditeur de niveaux et un éditeur de pixel art.
Tout est intégré et se suffit à lui-même !</p>
<img alt="Pico-8" class="align-right" src="./images/pico8editor.gif" />
<p>Grâce à tout ça, on peut se concentrer uniquement sur les aspects créatifs de notre jeu, comme le game design, le gameplay, le scénario, ou la musique.</p>
<p><strong>Facile à prendre en main !</strong></p>
<p>La cerise sur le gâteau est sa facilité de prise en main. Le combo <a class="reference external" href="http://www.lua.org/">Lua</a>/outils intégrés permet de très vite se lancer dans la création de ton premier jeu !
La communauté est excellente et il existe de <a class="reference external" href="https://www.youtube.com/watch?v=YXbR0eqPoAw&list=PLHKUrXMrDS5t3ibCCh412ZAy0slIv3jeE">très bon tutoriels sur le net</a>.</p>
<p><strong>Favorise le partage !</strong></p>
<p>Le fichier du jeu est la cartouche intégrale du jeu. Il est donc très simple de l'extraire et de la partager, que ça soit <a class="reference external" href="https://itch.io/games/tag-pico-8">sur itch.io</a>
ou sur sur le <a class="reference external" href="https://www.lexaloffle.com/bbs/?cat=7">hub Pico-8</a>.</p>
<p><strong>Parfait pour les Game Jams</strong></p>
<p>Pour finir, sa facilité de prise en main et son cadre imposé le rendent parfait pour les Game Jams. De gros projets ont utilisé Pico-8 dans des Game Jams
pour prototyper leur jeu, <a class="reference external" href="https://mattmakesgames.itch.io/celesteclassic">comme Celeste</a> !</p>
<p><strong>Mais non libre</strong></p>
<p>Malheurseusement, l'outil est non-libre et donc payant. Je n'ai pas l'habitude de promouvoir des logiciels payants, mais celui-ci est tellement bien
pensé que je pense que ça vaut réellement le coup. Si vraiment ça te bloque, tu peux te tourner vers un équivalent open source appelé <a class="reference external" href="https://tic80.com/">TIC-80</a>.</p>
<div class="section" id="mes-jeux">
<h2>Mes jeux</h2>
<p>Tu peux jeter un oeil aux jeux que j'ai codé pour des Game Jams avec <em>Pico-8</em>. J'ai tenté de découvrir différents styles de jeux :</p>
<ul class="simple">
<li><a class="reference external" href="https://dotmobo.itch.io/shoot-the-moon">Shoot The Moon</a>, un shooter spatial donc le code source est <a class="reference external" href="https://github.com/dotmobo/shoot-the-moon">disponible ici</a>.</li>
</ul>
<img alt="Shoot The Moon" src="./images/shootthemoon.gif" />
<ul class="simple">
<li><a class="reference external" href="https://dotmobo.itch.io/solene-is-missing">Solène Is Missing</a>, un mini rpg donc le code source est <a class="reference external" href="https://github.com/dotmobo/solene-is-missing">disponible là</a>.</li>
</ul>
<img alt="Solène Is Missing" src="./images/solene.gif" />
<ul class="simple">
<li><a class="reference external" href="https://dotmobo.itch.io/badaboom">Badaboom</a>, un petit jeu d'arcade à l'ancienne basé sur une scène du film <strong>Le 5ème élément</strong>, donc le code source est <a class="reference external" href="https://github.com/dotmobo/badaboom">disponible ici aussi</a>.</li>
</ul>
<img alt="Badaboom" src="./images/badaboom.gif" />
<ul class="simple">
<li><a class="reference external" href="https://dotmobo.itch.io/hellhound">Hellhound</a>, le prototyope d'un jeu de plateforme et de survie, donc le code source est <a class="reference external" href="https://github.com/dotmobo/hellhound">disponible là aussi</a>.</li>
</ul>
<img alt="Hellhound" src="./images/hellhound.gif" />
</div>
<div class="section" id="le-code">
<h2>Le code</h2>
<p>Au niveau du code, tu pourras trouver de nombreuses similarités avec <em>Löve</em> qu'on a <a class="reference external" href="http://dotmobo.github.io/plateformer-love2d.html">étudié précédemment</a>.</p>
<p>Tu as aussi les trois fonctions <strong>init</strong>, <strong>update</strong> et <strong>draw</strong> dans lesquelles tout ce passe.
De la même manière il te faudra gérer l'état de ton jeu et les différents écrans. Tu ne seras vraiment pas dépaysé !</p>
<p>Voici un exemple complet d'une cartouche, celle de <em>Hellhound</em>, pour te faire une idée !</p>
<div class="highlight"><pre><span></span><span class="n">pico</span><span class="o">-</span><span class="mi">8</span> <span class="n">cartridge</span> <span class="o">//</span> <span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">www</span><span class="p">.</span><span class="n">pico</span><span class="o">-</span><span class="mf">8.</span><span class="n">com</span>
<span class="n">version</span> <span class="mi">29</span>
<span class="n">__lua__</span>
<span class="c1">-- hellhound</span>
<span class="c1">-- mobo</span>
<span class="kr">function</span> <span class="nf">_init</span><span class="p">()</span>
<span class="n">state</span><span class="o">=</span><span class="mi">2</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">_update60</span><span class="p">()</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">state</span><span class="o">==</span><span class="mi">0</span><span class="p">)</span> <span class="n">update_game</span><span class="p">()</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">state</span><span class="o">==</span><span class="mi">1</span><span class="p">)</span> <span class="n">update_gameover</span><span class="p">()</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">state</span><span class="o">==</span><span class="mi">2</span><span class="p">)</span> <span class="n">update_gamestart</span><span class="p">()</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">_draw</span><span class="p">()</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">state</span><span class="o">==</span><span class="mi">0</span><span class="p">)</span> <span class="n">draw_game</span><span class="p">()</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">state</span><span class="o">==</span><span class="mi">1</span><span class="p">)</span> <span class="n">draw_gameover</span><span class="p">()</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">state</span><span class="o">==</span><span class="mi">2</span><span class="p">)</span> <span class="n">draw_gamestart</span><span class="p">()</span>
<span class="kr">end</span>
<span class="c1">-->8</span>
<span class="c1">-- game start</span>
<span class="kr">function</span> <span class="nf">update_gamestart</span><span class="p">()</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">btnp</span><span class="p">(</span><span class="err">⬆️</span><span class="p">))</span> <span class="n">init_game</span><span class="p">()</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">draw_gamestart</span><span class="p">()</span>
<span class="n">cls</span><span class="p">()</span>
<span class="n">map</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">128</span><span class="p">,</span> <span class="mi">64</span><span class="p">)</span>
<span class="n">camera</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">384</span><span class="p">)</span>
<span class="n">rectfill</span><span class="p">(</span><span class="mi">31</span><span class="p">,</span><span class="mi">73</span><span class="o">+</span><span class="mi">384</span><span class="p">,</span><span class="mi">105</span><span class="p">,</span><span class="mi">109</span><span class="o">+</span><span class="mi">384</span><span class="p">,</span><span class="mi">8</span><span class="p">)</span>
<span class="n">rectfill</span><span class="p">(</span><span class="mi">28</span><span class="p">,</span><span class="mi">70</span><span class="o">+</span><span class="mi">384</span><span class="p">,</span><span class="mi">102</span><span class="p">,</span><span class="mi">106</span><span class="o">+</span><span class="mi">384</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Hellhound"</span><span class="p">,</span><span class="mi">34</span><span class="p">,</span><span class="mi">76</span><span class="o">+</span><span class="mi">384</span><span class="p">,</span><span class="mi">8</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"⬆️ to start"</span><span class="p">,</span><span class="mi">34</span><span class="p">,</span><span class="mi">96</span><span class="o">+</span><span class="mi">384</span><span class="p">,</span><span class="mi">8</span><span class="p">)</span>
<span class="kr">end</span>
<span class="c1">-->8</span>
<span class="c1">-- game over</span>
<span class="kr">function</span> <span class="nf">update_gameover</span><span class="p">()</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">btnp</span><span class="p">(</span><span class="err">⬆️</span><span class="p">))</span> <span class="n">init_game</span><span class="p">()</span>
<span class="kr">if</span> <span class="n">music_start</span> <span class="kr">then</span>
<span class="n">music_start</span><span class="o">=</span><span class="kc">false</span>
<span class="n">music</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">draw_gameover</span><span class="p">()</span>
<span class="n">cls</span><span class="p">()</span>
<span class="n">map</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">128</span><span class="p">,</span> <span class="mi">64</span><span class="p">)</span>
<span class="n">camera</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">384</span><span class="p">)</span>
<span class="n">rectfill</span><span class="p">(</span><span class="mi">31</span><span class="p">,</span><span class="mi">73</span><span class="o">+</span><span class="mi">384</span><span class="p">,</span><span class="mi">105</span><span class="p">,</span><span class="mi">109</span><span class="o">+</span><span class="mi">384</span><span class="p">,</span><span class="mi">8</span><span class="p">)</span>
<span class="n">rectfill</span><span class="p">(</span><span class="mi">28</span><span class="p">,</span><span class="mi">70</span><span class="o">+</span><span class="mi">384</span><span class="p">,</span><span class="mi">102</span><span class="p">,</span><span class="mi">106</span><span class="o">+</span><span class="mi">384</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Game over"</span><span class="p">,</span><span class="mi">34</span><span class="p">,</span><span class="mi">76</span><span class="o">+</span><span class="mi">384</span><span class="p">,</span><span class="mi">8</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Score:"</span><span class="o">..</span><span class="n">ticks</span><span class="p">,</span><span class="mi">34</span><span class="p">,</span><span class="mi">86</span><span class="o">+</span><span class="mi">384</span><span class="p">,</span><span class="mi">8</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"⬆️ to retry"</span><span class="p">,</span><span class="mi">34</span><span class="p">,</span><span class="mi">96</span><span class="o">+</span><span class="mi">384</span><span class="p">,</span><span class="mi">8</span><span class="p">)</span>
<span class="kr">end</span>
<span class="c1">-->8</span>
<span class="c1">--game</span>
<span class="kr">function</span> <span class="nf">init_game</span><span class="p">()</span>
<span class="n">state</span><span class="o">=</span><span class="mi">0</span>
<span class="n">ticks</span><span class="o">=</span><span class="mi">0</span>
<span class="n">music_start</span><span class="o">=</span><span class="kc">false</span>
<span class="n">cam</span><span class="o">=</span><span class="n">create_camera</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">512</span><span class="o">-</span><span class="mi">128</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">32</span><span class="p">)</span>
<span class="n">p</span><span class="o">=</span><span class="n">create_player</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span><span class="mi">62</span><span class="o">*</span><span class="mi">8</span><span class="p">)</span>
<span class="n">bullets</span><span class="o">=</span><span class="n">create_bullets</span><span class="p">()</span>
<span class="n">enemies</span><span class="o">=</span><span class="n">create_enemies</span><span class="p">()</span>
<span class="n">explosions</span><span class="o">=</span><span class="n">create_explosions</span><span class="p">()</span>
<span class="n">spawns</span><span class="o">=</span><span class="p">{</span>
<span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">10</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">62</span><span class="o">*</span><span class="mi">8</span><span class="p">},{</span><span class="n">x</span><span class="o">=</span><span class="mi">13</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">62</span><span class="o">*</span><span class="mi">8</span><span class="p">},{</span><span class="n">x</span><span class="o">=</span><span class="mi">6</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">58</span><span class="o">*</span><span class="mi">8</span><span class="p">},</span>
<span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">6</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">54</span><span class="o">*</span><span class="mi">8</span><span class="p">},</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">3</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">51</span><span class="o">*</span><span class="mi">8</span><span class="p">},</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">1</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">51</span><span class="o">*</span><span class="mi">8</span><span class="p">},</span>
<span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">10</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">56</span><span class="o">*</span><span class="mi">8</span><span class="p">},</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">12</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">56</span><span class="o">*</span><span class="mi">8</span><span class="p">},</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">21</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">56</span><span class="o">*</span><span class="mi">8</span><span class="p">},</span>
<span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">21</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">62</span><span class="o">*</span><span class="mi">8</span><span class="p">},</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">25</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">62</span><span class="o">*</span><span class="mi">8</span><span class="p">},</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">29</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">62</span><span class="o">*</span><span class="mi">8</span><span class="p">},</span>
<span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">27</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">58</span><span class="o">*</span><span class="mi">8</span><span class="p">},</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">30</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">58</span><span class="o">*</span><span class="mi">8</span><span class="p">},</span> <span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">29</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">55</span><span class="o">*</span><span class="mi">8</span><span class="p">},</span>
<span class="p">{</span><span class="n">x</span><span class="o">=</span><span class="mi">29</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="mi">51</span><span class="o">*</span><span class="mi">8</span><span class="p">}</span>
<span class="p">}</span>
<span class="kr">for</span> <span class="n">s</span> <span class="kr">in</span> <span class="n">all</span><span class="p">(</span><span class="n">spawns</span><span class="p">)</span> <span class="kr">do</span>
<span class="n">enemies</span><span class="p">:</span><span class="n">spawn</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="n">s</span><span class="p">.</span><span class="n">y</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">update_game</span><span class="p">()</span>
<span class="n">ticks</span><span class="o">+=</span><span class="mi">1</span>
<span class="kr">if</span> <span class="ow">not</span> <span class="n">music_start</span> <span class="kr">then</span>
<span class="n">music</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">music_start</span><span class="o">=</span><span class="kc">true</span>
<span class="kr">end</span>
<span class="n">p</span><span class="p">:</span><span class="n">update</span><span class="p">()</span>
<span class="n">cam</span><span class="p">:</span><span class="n">update</span><span class="p">()</span>
<span class="n">bullets</span><span class="p">:</span><span class="n">update</span><span class="p">()</span>
<span class="n">enemies</span><span class="p">:</span><span class="n">update</span><span class="p">()</span>
<span class="n">explosions</span><span class="p">:</span><span class="n">update</span><span class="p">()</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">draw_game</span><span class="p">()</span>
<span class="n">cls</span><span class="p">(</span><span class="mi">7</span><span class="p">)</span>
<span class="n">palt</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="kc">false</span><span class="p">)</span>
<span class="n">palt</span><span class="p">(</span><span class="mi">15</span><span class="p">,</span> <span class="kc">true</span><span class="p">)</span>
<span class="c1">--camera</span>
<span class="n">cam</span><span class="p">:</span><span class="n">draw</span><span class="p">()</span>
<span class="c1">-- map</span>
<span class="n">map</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">128</span><span class="p">,</span> <span class="mi">64</span><span class="p">)</span>
<span class="c1">-- items</span>
<span class="n">p</span><span class="p">:</span><span class="n">draw</span><span class="p">()</span>
<span class="n">enemies</span><span class="p">:</span><span class="n">draw</span><span class="p">()</span>
<span class="n">bullets</span><span class="p">:</span><span class="n">draw</span><span class="p">()</span>
<span class="n">explosions</span><span class="p">:</span><span class="n">draw</span><span class="p">()</span>
<span class="c1">-- hud</span>
<span class="n">camera</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'score: '</span><span class="o">..</span><span class="n">ticks</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">)</span>
<span class="n">spr</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">120</span><span class="p">)</span>
<span class="kr">for</span> <span class="n">i</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="n">p</span><span class="p">.</span><span class="n">life</span> <span class="kr">do</span>
<span class="n">rectfill</span><span class="p">(</span><span class="mi">0</span><span class="o">+</span><span class="n">i</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span> <span class="mi">125</span><span class="p">,</span> <span class="mi">8</span><span class="o">+</span><span class="n">i</span><span class="o">*</span><span class="mi">8</span><span class="p">,</span> <span class="mi">127</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="c1">-->8</span>
<span class="c1">-- camera</span>
<span class="kr">function</span> <span class="nf">create_camera</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">,</span><span class="n">dx</span><span class="p">,</span><span class="n">dy</span><span class="p">,</span><span class="n">margin</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">c</span><span class="o">=</span><span class="p">{</span>
<span class="n">x</span><span class="o">=</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="n">y</span><span class="p">,</span><span class="n">dx</span><span class="o">=</span><span class="n">dx</span><span class="p">,</span><span class="n">dy</span><span class="o">=</span><span class="n">dy</span><span class="p">,</span><span class="n">speed</span><span class="o">=</span><span class="n">speed</span><span class="p">,</span><span class="n">margin</span><span class="o">=</span><span class="n">margin</span><span class="p">,</span>
<span class="n">update</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">x</span><span class="o">-</span><span class="n">cam</span><span class="p">.</span><span class="n">x</span><span class="o"><</span><span class="p">(</span><span class="mi">64</span><span class="o">-</span><span class="n">cam</span><span class="p">.</span><span class="n">margin</span><span class="p">))</span> <span class="kr">then</span>
<span class="n">cam</span><span class="p">.</span><span class="n">x</span><span class="o">=</span><span class="n">mid</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">cam</span><span class="p">.</span><span class="n">x</span><span class="o">-</span><span class="n">cam</span><span class="p">.</span><span class="n">dx</span><span class="p">,</span><span class="mi">1024</span><span class="o">-</span><span class="mi">128</span><span class="p">)</span>
<span class="kr">elseif</span> <span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">x</span><span class="o">-</span><span class="n">cam</span><span class="p">.</span><span class="n">x</span><span class="o">></span><span class="p">(</span><span class="mi">64</span><span class="o">+</span><span class="n">cam</span><span class="p">.</span><span class="n">margin</span><span class="p">))</span> <span class="kr">then</span>
<span class="n">cam</span><span class="p">.</span><span class="n">x</span><span class="o">=</span><span class="n">mid</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">cam</span><span class="p">.</span><span class="n">x</span><span class="o">+</span><span class="n">cam</span><span class="p">.</span><span class="n">dx</span><span class="p">,</span><span class="mi">1024</span><span class="o">-</span><span class="mi">128</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">y</span><span class="o">-</span><span class="n">cam</span><span class="p">.</span><span class="n">y</span><span class="o"><</span><span class="p">(</span><span class="mi">64</span><span class="o">-</span><span class="n">cam</span><span class="p">.</span><span class="n">margin</span><span class="p">))</span> <span class="kr">then</span>
<span class="n">cam</span><span class="p">.</span><span class="n">y</span><span class="o">=</span><span class="n">mid</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">cam</span><span class="p">.</span><span class="n">y</span><span class="o">-</span><span class="n">cam</span><span class="p">.</span><span class="n">dy</span><span class="p">,</span><span class="mi">512</span><span class="o">-</span><span class="mi">128</span><span class="p">)</span>
<span class="kr">elseif</span> <span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">y</span><span class="o">-</span><span class="n">cam</span><span class="p">.</span><span class="n">y</span><span class="o">></span><span class="p">(</span><span class="mi">64</span><span class="o">+</span><span class="n">cam</span><span class="p">.</span><span class="n">margin</span><span class="p">))</span> <span class="kr">then</span>
<span class="n">cam</span><span class="p">.</span><span class="n">y</span><span class="o">=</span><span class="n">mid</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">cam</span><span class="p">.</span><span class="n">y</span><span class="o">+</span><span class="n">cam</span><span class="p">.</span><span class="n">dy</span><span class="p">,</span><span class="mi">512</span><span class="o">-</span><span class="mi">128</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span><span class="p">,</span>
<span class="n">draw</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="n">camera</span><span class="p">(</span><span class="n">cam</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="n">cam</span><span class="p">.</span><span class="n">y</span><span class="p">)</span>
<span class="kr">end</span>
<span class="p">}</span>
<span class="kr">return</span> <span class="n">c</span>
<span class="kr">end</span>
<span class="c1">-->8</span>
<span class="c1">-- player</span>
<span class="kr">function</span> <span class="nf">create_player</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">p</span><span class="o">=</span><span class="p">{</span>
<span class="n">x</span><span class="o">=</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="n">y</span><span class="p">,</span><span class="n">w</span><span class="o">=</span><span class="mi">8</span><span class="p">,</span><span class="n">h</span><span class="o">=</span><span class="mi">8</span><span class="p">,</span><span class="n">dx</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="n">dy</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="n">acc</span><span class="o">=</span><span class="mf">0.08</span><span class="p">,</span><span class="n">dcc</span><span class="o">=</span><span class="mf">0.9</span><span class="p">,</span><span class="n">max_dx</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="n">max_dy</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span>
<span class="n">gravity</span><span class="o">=</span><span class="mf">0.15</span><span class="p">,</span><span class="n">jump_speed</span><span class="o">=-</span><span class="mf">1.75</span><span class="p">,</span><span class="n">jump_hold_time</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="n">ungrounded_time</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="n">max_jump_press</span><span class="o">=</span><span class="mi">15</span><span class="p">,</span>
<span class="n">jump_forgiveness</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span><span class="n">grounded</span><span class="o">=</span><span class="kc">false</span><span class="p">,</span><span class="n">is_jumping</span><span class="o">=</span><span class="kc">false</span><span class="p">,</span><span class="n">flip</span><span class="o">=</span><span class="kc">false</span><span class="p">,</span>
<span class="n">anims</span><span class="o">=</span><span class="n">anims</span><span class="p">(</span><span class="n">self</span><span class="p">),</span><span class="n">curanim</span><span class="o">=</span><span class="s2">"idle"</span><span class="p">,</span><span class="n">curframe</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="n">animtick</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span><span class="n">attack_tick</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
<span class="n">life</span><span class="o">=</span><span class="mi">15</span><span class="p">,</span>
<span class="n">set_anim</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">,</span><span class="n">anim</span><span class="p">)</span>
<span class="kr">if</span><span class="p">(</span><span class="n">anim</span><span class="o">==</span><span class="n">self</span><span class="p">.</span><span class="n">curanim</span><span class="p">)</span><span class="kr">return</span><span class="c1">--early out.</span>
<span class="kd">local</span> <span class="n">a</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">anims</span><span class="p">[</span><span class="n">anim</span><span class="p">]</span>
<span class="n">self</span><span class="p">.</span><span class="n">animtick</span><span class="o">=</span><span class="n">a</span><span class="p">.</span><span class="n">ticks</span><span class="c1">--ticks count down.</span>
<span class="n">self</span><span class="p">.</span><span class="n">curanim</span><span class="o">=</span><span class="n">anim</span>
<span class="n">self</span><span class="p">.</span><span class="n">curframe</span><span class="o">=</span><span class="mi">1</span>
<span class="kr">end</span><span class="p">,</span>
<span class="n">update</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="n">move</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="n">jump</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="n">fall</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="n">attack</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="n">animate</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="n">hurt</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kr">end</span><span class="p">,</span>
<span class="n">draw</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">a</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">anims</span><span class="p">[</span><span class="n">self</span><span class="p">.</span><span class="n">curanim</span><span class="p">]</span>
<span class="kd">local</span> <span class="n">frame</span><span class="o">=</span><span class="n">a</span><span class="p">.</span><span class="n">frames</span><span class="p">[</span><span class="n">self</span><span class="p">.</span><span class="n">curframe</span><span class="p">]</span>
<span class="n">spr</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">y</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="n">self</span><span class="p">.</span><span class="n">flip</span><span class="p">)</span>
<span class="kr">end</span>
<span class="p">}</span>
<span class="kr">return</span> <span class="n">p</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">move</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">btn</span><span class="p">(</span><span class="err">⬅️</span><span class="p">)</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">.</span><span class="n">dx</span><span class="o">-=</span><span class="n">self</span><span class="p">.</span><span class="n">acc</span>
<span class="n">self</span><span class="p">.</span><span class="n">flip</span><span class="o">=</span><span class="kc">true</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">grounded</span><span class="p">)</span> <span class="n">self</span><span class="p">:</span><span class="n">set_anim</span><span class="p">(</span><span class="s2">"run"</span><span class="p">)</span>
<span class="kr">elseif</span> <span class="n">btn</span><span class="p">(</span><span class="err">➡️</span><span class="p">)</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">.</span><span class="n">dx</span><span class="o">+=</span><span class="n">self</span><span class="p">.</span><span class="n">acc</span>
<span class="n">self</span><span class="p">.</span><span class="n">flip</span><span class="o">=</span><span class="kc">false</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">grounded</span><span class="p">)</span> <span class="n">self</span><span class="p">:</span><span class="n">set_anim</span><span class="p">(</span><span class="s2">"run"</span><span class="p">)</span>
<span class="kr">else</span>
<span class="n">self</span><span class="p">.</span><span class="n">dx</span><span class="o">*=</span><span class="n">self</span><span class="p">.</span><span class="n">dcc</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">grounded</span> <span class="ow">and</span> <span class="n">self</span><span class="p">.</span><span class="n">dx</span><span class="o">></span><span class="mi">0</span><span class="p">)</span> <span class="n">self</span><span class="p">:</span><span class="n">set_anim</span><span class="p">(</span><span class="s2">"slide"</span><span class="p">)</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">grounded</span> <span class="ow">and</span> <span class="n">self</span><span class="p">.</span><span class="n">dx</span><span class="o"><=</span><span class="mi">0</span><span class="p">)</span> <span class="n">self</span><span class="p">:</span><span class="n">set_anim</span><span class="p">(</span><span class="s2">"idle"</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">if</span> <span class="n">is_solid</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">x</span><span class="o">+</span><span class="n">self</span><span class="p">.</span><span class="n">dx</span><span class="p">,</span><span class="n">self</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">.</span><span class="n">dx</span><span class="o">=</span><span class="mi">0</span>
<span class="kr">end</span>
<span class="n">self</span><span class="p">.</span><span class="n">dx</span><span class="o">=</span><span class="n">mid</span><span class="p">(</span><span class="o">-</span><span class="n">self</span><span class="p">.</span><span class="n">max_dx</span><span class="p">,</span><span class="n">self</span><span class="p">.</span><span class="n">dx</span><span class="p">,</span><span class="n">self</span><span class="p">.</span><span class="n">max_dx</span><span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">x</span><span class="o">+=</span><span class="n">self</span><span class="p">.</span><span class="n">dx</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">jump</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">btn</span><span class="p">(</span><span class="err">🅾️</span><span class="p">)</span> <span class="kr">then</span>
<span class="kd">local</span> <span class="n">canJump</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">grounded</span> <span class="ow">or</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">ungrounded_time</span> <span class="o">></span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">self</span><span class="p">.</span><span class="n">ungrounded_time</span> <span class="o"><=</span> <span class="n">self</span><span class="p">.</span><span class="n">jump_forgiveness</span><span class="p">)</span> <span class="ow">and</span> <span class="ow">not</span> <span class="n">self</span><span class="p">.</span><span class="n">is_jumping</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">.</span><span class="n">jump_hold_time</span><span class="o">></span><span class="mi">0</span> <span class="ow">or</span> <span class="n">canJump</span> <span class="kr">then</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">jump_hold_time</span><span class="o">==</span><span class="mi">1</span><span class="p">)</span> <span class="n">sfx</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">is_jumping</span><span class="o">=</span><span class="kc">true</span>
<span class="n">self</span><span class="p">.</span><span class="n">jump_hold_time</span><span class="o">+=</span><span class="mi">1</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">.</span><span class="n">jump_hold_time</span><span class="o"><</span><span class="n">self</span><span class="p">.</span><span class="n">max_jump_press</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">.</span><span class="n">dy</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">jump_speed</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">else</span>
<span class="n">self</span><span class="p">.</span><span class="n">jump_hold_time</span><span class="o">=</span><span class="mi">0</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">fall</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">dy</span><span class="o">+=</span><span class="n">self</span><span class="p">.</span><span class="n">gravity</span>
<span class="kr">if</span> <span class="n">is_solid</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="n">self</span><span class="p">.</span><span class="n">y</span><span class="o">+</span><span class="n">self</span><span class="p">.</span><span class="n">dy</span><span class="p">)</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">.</span><span class="n">dy</span><span class="o">=</span><span class="mi">0</span>
<span class="n">self</span><span class="p">.</span><span class="n">grounded</span><span class="o">=</span><span class="kc">true</span>
<span class="n">self</span><span class="p">.</span><span class="n">is_jumping</span> <span class="o">=</span> <span class="kc">false</span>
<span class="n">self</span><span class="p">.</span><span class="n">ungrounded_time</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kr">else</span>
<span class="n">self</span><span class="p">.</span><span class="n">grounded</span><span class="o">=</span><span class="kc">false</span>
<span class="n">self</span><span class="p">.</span><span class="n">ungrounded_time</span><span class="o">+=</span><span class="mi">1</span>
<span class="kr">end</span>
<span class="kr">if</span><span class="p">(</span><span class="ow">not</span> <span class="n">self</span><span class="p">.</span><span class="n">grounded</span><span class="p">)</span> <span class="n">self</span><span class="p">:</span><span class="n">set_anim</span><span class="p">(</span><span class="s2">"jump"</span><span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">dy</span><span class="o">=</span><span class="n">mid</span><span class="p">(</span><span class="o">-</span><span class="n">self</span><span class="p">.</span><span class="n">max_dy</span><span class="p">,</span><span class="n">self</span><span class="p">.</span><span class="n">dy</span><span class="p">,</span><span class="n">self</span><span class="p">.</span><span class="n">max_dy</span><span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">y</span><span class="o">+=</span><span class="n">self</span><span class="p">.</span><span class="n">dy</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">attack</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">btn</span><span class="p">(</span><span class="err">❎</span><span class="p">)</span> <span class="kr">then</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">attack_tick</span><span class="o">%</span><span class="mi">15</span><span class="o">==</span><span class="mi">0</span><span class="p">)</span> <span class="kr">then</span>
<span class="n">bullets</span><span class="p">:</span><span class="n">shoot</span><span class="p">()</span>
<span class="kr">end</span>
<span class="kr">if</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">grounded</span><span class="p">)</span> <span class="n">self</span><span class="p">:</span><span class="n">set_anim</span><span class="p">(</span><span class="s2">"attack"</span><span class="p">)</span>
<span class="kr">if</span><span class="p">(</span><span class="ow">not</span> <span class="n">self</span><span class="p">.</span><span class="n">grounded</span><span class="p">)</span> <span class="n">self</span><span class="p">:</span><span class="n">set_anim</span><span class="p">(</span><span class="s2">"airattack"</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">attack_tick</span><span class="o">+=</span><span class="mi">1</span>
<span class="kr">else</span>
<span class="n">p</span><span class="p">.</span><span class="n">attack_tick</span><span class="o">=</span><span class="mi">0</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">anims</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kr">return</span> <span class="p">{</span>
<span class="p">[</span><span class="s2">"idle"</span><span class="p">]</span><span class="o">=</span><span class="p">{</span><span class="n">ticks</span><span class="o">=</span><span class="mi">15</span><span class="p">,</span><span class="n">frames</span><span class="o">=</span><span class="p">{</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">}},</span>
<span class="p">[</span><span class="s2">"run"</span><span class="p">]</span><span class="o">=</span><span class="p">{</span><span class="n">ticks</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span><span class="n">frames</span><span class="o">=</span><span class="p">{</span><span class="mi">5</span><span class="p">,</span><span class="mi">6</span><span class="p">}},</span>
<span class="p">[</span><span class="s2">"jump"</span><span class="p">]</span><span class="o">=</span><span class="p">{</span><span class="n">ticks</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="n">frames</span><span class="o">=</span><span class="p">{</span><span class="mi">10</span><span class="p">}},</span>
<span class="p">[</span><span class="s2">"slide"</span><span class="p">]</span><span class="o">=</span><span class="p">{</span><span class="n">ticks</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="n">frames</span><span class="o">=</span><span class="p">{</span><span class="mi">1</span><span class="p">}},</span>
<span class="p">[</span><span class="s2">"attack"</span><span class="p">]</span><span class="o">=</span><span class="p">{</span><span class="n">ticks</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="n">frames</span><span class="o">=</span><span class="p">{</span><span class="mi">7</span><span class="p">}},</span>
<span class="p">[</span><span class="s2">"airattack"</span><span class="p">]</span><span class="o">=</span><span class="p">{</span><span class="n">ticks</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="n">frames</span><span class="o">=</span><span class="p">{</span><span class="mi">11</span><span class="p">}},</span>
<span class="p">[</span><span class="s2">"climb"</span><span class="p">]</span><span class="o">=</span><span class="p">{</span><span class="n">ticks</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span><span class="n">frames</span><span class="o">=</span><span class="p">{</span><span class="mi">8</span><span class="p">,</span><span class="mi">9</span><span class="p">}},</span>
<span class="p">}</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">hurt</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">ticks</span><span class="o">%</span><span class="mi">35</span><span class="o">==</span><span class="mi">0</span> <span class="kr">then</span>
<span class="n">p</span><span class="p">.</span><span class="n">life</span><span class="o">-=</span><span class="mi">1</span>
<span class="kr">end</span>
<span class="c1">--death</span>
<span class="kr">if</span> <span class="n">p</span><span class="p">.</span><span class="n">life</span><span class="o"><=</span><span class="mi">0</span> <span class="kr">then</span>
<span class="n">sfx</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="n">state</span><span class="o">=</span><span class="mi">1</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="c1">-->8</span>
<span class="c1">-- bullets</span>
<span class="kr">function</span> <span class="nf">create_bullets</span><span class="p">()</span>
<span class="kr">return</span> <span class="p">{</span>
<span class="n">items</span><span class="o">=</span><span class="p">{},</span>
<span class="n">update</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kr">for</span> <span class="n">b</span> <span class="kr">in</span> <span class="n">all</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="kr">do</span>
<span class="n">b</span><span class="p">.</span><span class="n">x</span><span class="o">+=</span><span class="n">b</span><span class="p">.</span><span class="n">speed</span>
<span class="n">animate</span><span class="p">(</span><span class="n">b</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">b</span><span class="p">.</span><span class="n">x</span><span class="o"><</span><span class="mi">0</span> <span class="ow">or</span> <span class="n">b</span><span class="p">.</span><span class="n">x</span><span class="o">></span><span class="mi">1024</span> <span class="ow">or</span> <span class="n">is_solid</span><span class="p">(</span><span class="n">b</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="n">b</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="kr">then</span>
<span class="n">del</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">items</span><span class="p">,</span><span class="n">b</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">end</span><span class="p">,</span>
<span class="n">draw</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kr">for</span> <span class="n">b</span> <span class="kr">in</span> <span class="n">all</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="kr">do</span>
<span class="kd">local</span> <span class="n">a</span><span class="o">=</span><span class="n">b</span><span class="p">.</span><span class="n">anims</span><span class="p">[</span><span class="n">b</span><span class="p">.</span><span class="n">curanim</span><span class="p">]</span>
<span class="kd">local</span> <span class="n">frame</span><span class="o">=</span><span class="n">a</span><span class="p">.</span><span class="n">frames</span><span class="p">[</span><span class="n">b</span><span class="p">.</span><span class="n">curframe</span><span class="p">]</span>
<span class="n">spr</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span><span class="n">b</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="n">b</span><span class="p">.</span><span class="n">y</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="n">b</span><span class="p">.</span><span class="n">flip</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span><span class="p">,</span>
<span class="n">shoot</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="n">sfx</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">new_bullet</span><span class="o">=</span><span class="p">{</span>
<span class="n">x</span><span class="o">=</span><span class="n">p</span><span class="p">.</span><span class="n">flip</span> <span class="ow">and</span> <span class="n">p</span><span class="p">.</span><span class="n">x</span><span class="o">-</span><span class="mi">4</span> <span class="ow">or</span> <span class="n">p</span><span class="p">.</span><span class="n">x</span><span class="o">+</span><span class="mi">4</span><span class="p">,</span>
<span class="n">y</span><span class="o">=</span><span class="n">p</span><span class="p">.</span><span class="n">y</span><span class="p">,</span>
<span class="n">speed</span><span class="o">=</span><span class="n">p</span><span class="p">.</span><span class="n">flip</span> <span class="ow">and</span> <span class="o">-</span><span class="mf">1.5</span> <span class="ow">or</span> <span class="mf">1.5</span><span class="p">,</span>
<span class="n">flip</span><span class="o">=</span><span class="n">p</span><span class="p">.</span><span class="n">flip</span><span class="p">,</span>
<span class="n">anims</span><span class="o">=</span><span class="p">{[</span><span class="s2">"shoot"</span><span class="p">]</span><span class="o">=</span><span class="p">{</span><span class="n">ticks</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span><span class="n">frames</span><span class="o">=</span><span class="p">{</span><span class="mi">12</span><span class="p">,</span><span class="mi">13</span><span class="p">,</span><span class="mi">14</span><span class="p">,</span><span class="mi">15</span><span class="p">}}},</span>
<span class="n">curanim</span><span class="o">=</span><span class="s2">"shoot"</span><span class="p">,</span><span class="n">curframe</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="n">animtick</span><span class="o">=</span><span class="mi">0</span>
<span class="p">}</span>
<span class="n">add</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">items</span><span class="p">,</span> <span class="n">new_bullet</span><span class="p">)</span>
<span class="kr">end</span>
<span class="p">}</span>
<span class="kr">end</span>
<span class="c1">-->8</span>
<span class="c1">-- enemies</span>
<span class="kr">function</span> <span class="nf">create_enemies</span><span class="p">()</span>
<span class="kr">return</span> <span class="p">{</span>
<span class="n">items</span><span class="o">=</span><span class="p">{},</span>
<span class="n">update</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kr">for</span> <span class="n">e</span> <span class="kr">in</span> <span class="n">all</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="kr">do</span>
<span class="kr">if</span> <span class="p">(</span><span class="ow">not</span> <span class="n">e</span><span class="p">.</span><span class="n">flip</span><span class="p">)</span> <span class="n">e</span><span class="p">.</span><span class="n">x</span><span class="o">+=</span><span class="n">e</span><span class="p">.</span><span class="n">speed</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">flip</span><span class="p">)</span> <span class="n">e</span><span class="p">.</span><span class="n">x</span><span class="o">-=</span><span class="n">e</span><span class="p">.</span><span class="n">speed</span>
<span class="n">animate</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">is_solid</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="n">e</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">is_solid</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="n">e</span><span class="p">.</span><span class="n">y</span><span class="o">+</span><span class="mi">8</span><span class="p">)</span> <span class="kr">then</span>
<span class="n">e</span><span class="p">.</span><span class="n">flip</span><span class="o">=</span><span class="ow">not</span> <span class="n">e</span><span class="p">.</span><span class="n">flip</span>
<span class="kr">end</span>
<span class="c1">-- killed ?</span>
<span class="kr">for</span> <span class="n">b</span> <span class="kr">in</span> <span class="n">all</span><span class="p">(</span><span class="n">bullets</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="kr">do</span>
<span class="kr">if</span> <span class="n">collide</span><span class="p">(</span><span class="n">e</span><span class="p">,</span><span class="n">b</span><span class="p">)</span> <span class="kr">then</span>
<span class="n">explosions</span><span class="p">:</span><span class="n">add</span><span class="p">(</span><span class="n">b</span><span class="p">.</span><span class="n">x</span><span class="o">+</span><span class="mi">4</span><span class="p">,</span><span class="n">b</span><span class="p">.</span><span class="n">y</span><span class="o">+</span><span class="mi">2</span><span class="p">)</span>
<span class="n">del</span><span class="p">(</span><span class="n">bullets</span><span class="p">.</span><span class="n">items</span><span class="p">,</span><span class="n">b</span><span class="p">)</span>
<span class="n">e</span><span class="p">.</span><span class="n">life</span><span class="o">-=</span><span class="mi">1</span>
<span class="kr">if</span> <span class="n">e</span><span class="p">.</span><span class="n">life</span><span class="o">==</span><span class="mi">0</span> <span class="kr">then</span>
<span class="kr">if</span> <span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">life</span><span class="o"><</span><span class="mi">15</span><span class="p">)</span> <span class="n">p</span><span class="p">.</span><span class="n">life</span><span class="o">+=</span><span class="mi">1</span>
<span class="n">sfx</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">del</span><span class="p">(</span><span class="n">enemies</span><span class="p">.</span><span class="n">items</span><span class="p">,</span><span class="n">e</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">random_spawn</span><span class="o">=</span><span class="n">rnd</span><span class="p">(</span><span class="n">spawns</span><span class="p">)</span>
<span class="n">enemies</span><span class="p">:</span><span class="n">spawn</span><span class="p">(</span><span class="n">random_spawn</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="n">random_spawn</span><span class="p">.</span><span class="n">y</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">end</span><span class="p">,</span>
<span class="n">draw</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kr">for</span> <span class="n">e</span> <span class="kr">in</span> <span class="n">all</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="kr">do</span>
<span class="kd">local</span> <span class="n">a</span><span class="o">=</span><span class="n">e</span><span class="p">.</span><span class="n">anims</span><span class="p">[</span><span class="n">e</span><span class="p">.</span><span class="n">curanim</span><span class="p">]</span>
<span class="kd">local</span> <span class="n">frame</span><span class="o">=</span><span class="n">a</span><span class="p">.</span><span class="n">frames</span><span class="p">[</span><span class="n">e</span><span class="p">.</span><span class="n">curframe</span><span class="p">]</span>
<span class="n">spr</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span><span class="n">e</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="n">e</span><span class="p">.</span><span class="n">y</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="n">e</span><span class="p">.</span><span class="n">flip</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span><span class="p">,</span>
<span class="n">spawn</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">,</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">new_enemy</span><span class="o">=</span><span class="p">{</span>
<span class="n">x</span><span class="o">=</span><span class="n">x</span><span class="p">,</span>
<span class="n">y</span><span class="o">=</span><span class="n">y</span><span class="p">,</span>
<span class="n">speed</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
<span class="n">life</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
<span class="n">flip</span><span class="o">=</span><span class="kc">false</span><span class="p">,</span>
<span class="n">anims</span><span class="o">=</span><span class="p">{[</span><span class="s2">"run"</span><span class="p">]</span><span class="o">=</span><span class="p">{</span><span class="n">ticks</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span><span class="n">frames</span><span class="o">=</span><span class="n">rnd</span><span class="p">{{</span><span class="mi">17</span><span class="p">,</span><span class="mi">18</span><span class="p">},{</span><span class="mi">33</span><span class="p">,</span><span class="mi">34</span><span class="p">},{</span><span class="mi">49</span><span class="p">,</span><span class="mi">50</span><span class="p">},{</span><span class="mi">19</span><span class="p">,</span><span class="mi">20</span><span class="p">},{</span><span class="mi">35</span><span class="p">,</span><span class="mi">36</span><span class="p">}}}},</span>
<span class="n">curanim</span><span class="o">=</span><span class="s2">"run"</span><span class="p">,</span><span class="n">curframe</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span><span class="n">animtick</span><span class="o">=</span><span class="mi">0</span>
<span class="p">}</span>
<span class="n">add</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">items</span><span class="p">,</span> <span class="n">new_enemy</span><span class="p">)</span>
<span class="kr">end</span>
<span class="p">}</span>
<span class="kr">end</span>
<span class="c1">-->8</span>
<span class="c1">--explosions</span>
<span class="kr">function</span> <span class="nf">create_explosions</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">)</span>
<span class="kr">return</span> <span class="p">{</span>
<span class="n">items</span><span class="o">=</span><span class="p">{},</span>
<span class="n">update</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kr">for</span> <span class="n">e</span> <span class="kr">in</span> <span class="n">all</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="kr">do</span>
<span class="n">e</span><span class="p">.</span><span class="n">timer</span><span class="o">+=</span><span class="mi">1</span>
<span class="kr">if</span> <span class="n">e</span><span class="p">.</span><span class="n">timer</span><span class="o">==</span><span class="mi">13</span> <span class="kr">then</span>
<span class="n">del</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">items</span><span class="p">,</span><span class="n">e</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">end</span><span class="p">,</span>
<span class="n">draw</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="kr">for</span> <span class="n">e</span> <span class="kr">in</span> <span class="n">all</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">items</span><span class="p">)</span> <span class="kr">do</span>
<span class="n">circ</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="n">e</span><span class="p">.</span><span class="n">y</span><span class="p">,</span><span class="n">e</span><span class="p">.</span><span class="n">timer</span><span class="o">/</span><span class="mi">3</span><span class="p">,</span>
<span class="mi">8</span><span class="o">+</span><span class="n">e</span><span class="p">.</span><span class="n">timer</span><span class="o">%</span><span class="mi">3</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span><span class="p">,</span>
<span class="n">add</span><span class="o">=</span><span class="kr">function</span><span class="p">(</span><span class="n">self</span><span class="p">,</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">)</span>
<span class="n">add</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">items</span><span class="p">,{</span><span class="n">x</span><span class="o">=</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="n">y</span><span class="p">,</span><span class="n">timer</span><span class="o">=</span><span class="mi">0</span><span class="p">})</span>
<span class="kr">end</span>
<span class="p">}</span>
<span class="kr">end</span>
<span class="c1">-->8</span>
<span class="c1">-- misc</span>
<span class="kr">function</span> <span class="nf">is_solid</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">flag</span><span class="p">)</span>
<span class="n">flag</span> <span class="o">=</span> <span class="n">flag</span> <span class="ow">or</span> <span class="mi">0</span>
<span class="kr">if</span><span class="p">(</span><span class="n">fget</span><span class="p">(</span><span class="n">mget</span><span class="p">(</span><span class="n">flr</span><span class="p">(</span><span class="n">x</span><span class="o">/</span><span class="mi">8</span><span class="p">),</span><span class="n">flr</span><span class="p">(</span><span class="n">y</span><span class="o">/</span><span class="mi">8</span><span class="p">)),</span><span class="n">flag</span><span class="p">))</span><span class="kr">return</span> <span class="kc">true</span>
<span class="kr">if</span><span class="p">(</span><span class="n">fget</span><span class="p">(</span><span class="n">mget</span><span class="p">(</span><span class="n">flr</span><span class="p">((</span><span class="n">x</span><span class="o">+</span><span class="mi">7</span><span class="p">)</span><span class="o">/</span><span class="mi">8</span><span class="p">),</span><span class="n">flr</span><span class="p">(</span><span class="n">y</span><span class="o">/</span><span class="mi">8</span><span class="p">)),</span><span class="n">flag</span><span class="p">))</span><span class="kr">return</span> <span class="kc">true</span>
<span class="kr">if</span><span class="p">(</span><span class="n">fget</span><span class="p">(</span><span class="n">mget</span><span class="p">(</span><span class="n">flr</span><span class="p">(</span><span class="n">x</span><span class="o">/</span><span class="mi">8</span><span class="p">),</span><span class="n">flr</span><span class="p">((</span><span class="n">y</span><span class="o">+</span><span class="mi">7</span><span class="p">)</span><span class="o">/</span><span class="mi">8</span><span class="p">)),</span><span class="n">flag</span><span class="p">))</span><span class="kr">return</span> <span class="kc">true</span>
<span class="kr">if</span><span class="p">(</span><span class="n">fget</span><span class="p">(</span><span class="n">mget</span><span class="p">(</span><span class="n">flr</span><span class="p">((</span><span class="n">x</span><span class="o">+</span><span class="mi">7</span><span class="p">)</span><span class="o">/</span><span class="mi">8</span><span class="p">),</span><span class="n">flr</span><span class="p">((</span><span class="n">y</span><span class="o">+</span><span class="mi">7</span><span class="p">)</span><span class="o">/</span><span class="mi">8</span><span class="p">)),</span><span class="n">flag</span><span class="p">))</span><span class="kr">return</span> <span class="kc">true</span>
<span class="kr">return</span> <span class="kc">false</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">collide</span><span class="p">(</span><span class="n">a</span><span class="p">,</span><span class="n">b</span><span class="p">)</span>
<span class="kr">return</span> <span class="ow">not</span> <span class="p">(</span><span class="n">a</span><span class="p">.</span><span class="n">x</span><span class="o">></span><span class="n">b</span><span class="p">.</span><span class="n">x</span><span class="o">+</span><span class="mi">8</span>
<span class="ow">or</span> <span class="n">a</span><span class="p">.</span><span class="n">y</span><span class="o">></span><span class="n">b</span><span class="p">.</span><span class="n">y</span><span class="o">+</span><span class="mi">8</span>
<span class="ow">or</span> <span class="n">a</span><span class="p">.</span><span class="n">x</span><span class="o">+</span><span class="mi">8</span><span class="o"><</span><span class="n">b</span><span class="p">.</span><span class="n">x</span>
<span class="ow">or</span> <span class="n">a</span><span class="p">.</span><span class="n">y</span><span class="o">+</span><span class="mi">8</span><span class="o"><</span><span class="n">b</span><span class="p">.</span><span class="n">y</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">animate</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">animtick</span><span class="o">-=</span><span class="mi">1</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">.</span><span class="n">animtick</span><span class="o"><=</span><span class="mi">0</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">.</span><span class="n">curframe</span><span class="o">+=</span><span class="mi">1</span>
<span class="kd">local</span> <span class="n">a</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">anims</span><span class="p">[</span><span class="n">self</span><span class="p">.</span><span class="n">curanim</span><span class="p">]</span>
<span class="n">self</span><span class="p">.</span><span class="n">animtick</span><span class="o">=</span><span class="n">a</span><span class="p">.</span><span class="n">ticks</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">.</span><span class="n">curframe</span><span class="o">>#</span><span class="n">a</span><span class="p">.</span><span class="n">frames</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">.</span><span class="n">curframe</span><span class="o">=</span><span class="mi">1</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="n">__gfx__</span>
<span class="mi">00000000</span><span class="n">ffff8ff8ffffffffffff8ff8ffffffffffff8ff8ffff8ff8ffff8ff8f8080000f8080000ffff8ff8ffff8ff8ffffffffffffffffffffffffffffffff</span>
<span class="mi">00000000</span><span class="n">ffff0000ffff8ff8ffff0000ffff8ff8ffff0000ffff0000ffff0000ff000000ff000000ffff0000ffff0000fff2222ffff2222ffff2222ffff2222f</span>
<span class="mi">00700700</span><span class="n">ff000808ff000000ff000808ff000000ff000808ff000808ff000808ff080000ff080000ff000808ff000808ff288882ff228882ff222882ff228882</span>
<span class="mi">00077000</span><span class="n">f0000000f0000808f0000000f0000808f0000000f0000000f0000000f8000000f8000000f0000000f0000000f2899aa8f22899a8f2228998f22899a8</span>
<span class="mi">00077000</span><span class="n">f0000000f0000000f0000787f0000000f0000000f0000000f0000787fff00000fff00000f0000000f00007872899aaa822899aa8222899a822899aa8</span>
<span class="mi">0070070000000000</span><span class="n">f000000000000888f0000787000000000000000000000888fff00000fff000000000000000000888f2899aa8f22899a8f2228998f22899a8</span>
<span class="mi">00000000</span><span class="n">f000000000000000f000000000000888f0000000f0000000f0000000ffff0000ffff0000f0000000f0000000ff288882ff228882ff222882ff228882</span>
<span class="mi">00000000</span><span class="n">ff0f0ff0ff0f0ff0ff0f0ff0ff0f0ff0ff0f0ff0f0f0ff0fff0f0ff0ffffff0ffffffff0fffffffffffffffffff2222ffff2222ffff2222ffff2222f</span>
<span class="n">ffffffffff444444ff444444ff333333ff3333330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="n">fffffffff44eeeeef44eeeeef33eeeeef33eeeee0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="n">fffffffff4eee1e1f4eee1e1f3eee1e1f3eee1e10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="n">faafaafff44eeeeef44eee8ef33eeeeef33eee8e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="n">faaa7afff44eeeeef44eee8ef33eeeeef33eee8e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="n">faaaaafff3333333fe33333ef5555555fe55555e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="n">ffaaaffffe33333ef3333333fe55555ef55555550000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="n">fffaffffffeefeeffffeefeeffeefeeffffeefee0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">ff999999ff999999ff888888ff8888880000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">f99eeeeef99eeeeef88eeeeef88eeeee0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">f9eee1e1f9eee1e1f8eee1e1f8eee1e10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">f99eeeeef99eee8ef88eeeeef88eee8e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">f99eeeeef99eee8ef88eeeeef88eee8e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">f4444444fe44444efcccccccfeccccce0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">fe44444ef4444444fecccccefccccccc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">ffeefeeffffeefeeffeefeeffffeefee0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">ff555555ff55555500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">f55eeeeef55eeeee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">f5eee1e1f5eee1e100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">f55eeeeef55eee8e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">f55eeeeef55eee8e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">f9999999fe99999e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">fe99999ef999999900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">00000000</span><span class="n">ffeefeeffffeefee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">55555555000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000882222222222288200000000</span>
<span class="mi">55555555000002200200022002000000000200200220020000200000000001100100011001000000000100100110010000100000828822222228828200000000</span>
<span class="mi">55555555002200000002022000000200000002200220022000002200001100000001011000000100000001100110011000001100282882222288282200000000</span>
<span class="mi">55555555002200000000000000000000000000000000000000002200001100000000000000000000000000000000000000001100282288222882282200000000</span>
<span class="mi">55555555000000002020202020202020202020202020202000000000000000001010101010101010101010101010101000000000228228828822822200000000</span>
<span class="mi">55555555020000222222222222222222222222222222222222000020010000111111111111111111111111111111111111000010228222282222822200000000</span>
<span class="mi">55555555000002222822228888222282222228822288282222200020000001111</span><span class="n">c1111cccc1111c111111cc111cc1c1111100010222828828828222200000000</span>
<span class="mi">5555555500000222882882882828288222882882828828822220000000000111</span><span class="n">cc1cc1cc1c1c1cc111cc1cc1c1cc1cc111100000228882222288822200000000</span>
<span class="mi">00000000000002882222222222222222222222222222222222220000000001</span><span class="n">cc1111111111111111111111111111111111110000288282222282882200000000</span>
<span class="mi">000000000220228822</span><span class="n">eee22222666662222222222266652222200000011011cc11ddd11111666661111111111166651111100000888888888888888200000000</span>
<span class="mi">00000000022002222</span><span class="n">ee8ee2226666666222222222660665282220000011001111dd8dd11166666661111111116606651c1110000222282222282222200000000</span>
<span class="mi">0000000000002228</span><span class="n">ee898ee2260060062222222266606665822000200000111cdd898dd1160060061111111166606665c1100010222228222822222200000000</span>
<span class="mf">0000000000200228e89</span><span class="n">a98e2266060662222222266000665222200000010011cd89a98d116606066111111116600066511110000222228222822222200000000</span>
<span class="mf">00000000000022222e444</span><span class="n">e2222666662622226226660666588200200000011111d444d11116666616111161166606665cc100100222222828222222200000000</span>
<span class="mf">000000000200028822e4</span><span class="n">e22222626262266662226666666588220220010001cc11d4d111116161611666611166666665cc110110222222828222222200000000</span>
<span class="mi">000000000000222822242222222222226222262266666665222000000000111</span><span class="n">c1114111111111111611116116666666511100000222222282222222200000000</span>
<span class="mi">222222220000022222222222222222222222222222222222822200000000011111111111111111111111111111111111</span><span class="n">c1110000cc11111111111cc100000000</span>
<span class="mi">22222222000022882222222222222222222222222222222222200220000011</span><span class="n">cc1111111111111111111111111111111111100110c1cc1111111cc1c100000000</span>
<span class="mi">222222220020022822222222222222222222222222222222882202200010011</span><span class="n">c11111111111111111111111111111111cc1101101c1cc11111cc1c1100000000</span>
<span class="mi">222222220000222222222222222222222222222222222222882000000000111111111111111111111111111111111111</span><span class="n">cc1000001c11cc111cc11c1100000000</span>
<span class="mi">222222220000022822222222222222222222222222222222222200000000011</span><span class="n">c111111111111111111111111111111111111000011c11cc1cc11c11100000000</span>
<span class="mi">222222220000222222222222222222222222222222222222882002200000111111111111111111111111111111111111</span><span class="n">cc10011011c1111c1111c11100000000</span>
<span class="mi">22222222020002882222222222222222222222222222222282220200010001</span><span class="n">cc11111111111111111111111111111111c1110100111c1cc1cc1c111100000000</span>
<span class="mi">22222222000022822222222222222222222222222222222222200000000011</span><span class="n">c1111111111111111111111111111111111110000011ccc11111ccc11100000000</span>
<span class="mi">1111111100000222882882882882828228828822288288282220000000000111</span><span class="n">cc1cc1cc1cc1c1c11cc1cc111cc1cc1c111000001cc1c11111c1cc1100000000</span>
<span class="mi">1111111102000222882222822822228828822222228288222220000001000111</span><span class="n">cc1111c11c1111cc1cc1111111c1cc1111100000ccccccccccccccc100000000</span>
<span class="mi">111111110200002222222222222222222222222222222222220000200100001111111111111111111111111111111111110000101111</span><span class="n">c11111c1111100000000</span>
<span class="mi">1111111100000000020202020202020202020202020202020000000000000000010101010101010101010101010101010000000011111</span><span class="n">c111c11111100000000</span>
<span class="mi">1111111100220000000000000000000000000000000000000000220000110000000000000000000000000000000000000000110011111</span><span class="n">c111c11111100000000</span>
<span class="mi">11111111002200000220200000200000022000000220022000002200001100000110100000100000011000000110011000001100111111</span><span class="n">c1c111111100000000</span>
<span class="mi">11111111000002000220002000000020020020000020022002200000000001000110001000000010010010000010011001100000111111</span><span class="n">c1c111111100000000</span>
<span class="mi">111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111</span><span class="n">c1111111100000000</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040404040004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004040404040000000000000004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000040400040000000000000000000000000000000000000000000000000000000000000004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000000000040404040000000000000000000000000000000000000004040404000404040004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000004040404040000040000000000000000000000000000000000000000000000000000000004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000040404040400040000040400000000000000000000000000000000000404040400000000000004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000000000040000000400000000000000000000000000000000000000000000000000000404</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000404040400000004040004040400000000000000040000000404000000000000000000000000000000000000000004040400040004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000000000040000000004040400000000000000000000000000000000000000000000000004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000404040404040000000000000404040400000000000000000000000000000000040400000004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000040404040400000000000000000000000000000000040000000000000000000404000000000000000000000004040404040000000004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000000000040000000000000000000004040400000000000000000000000000000000000004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000404040400040000040000000000000000000000000404000000000000000000040404040400000004</span>
<span class="mi">05050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000404040400000004040400000000000000000000000000040000040000000000000000000000000000040404000000000000000000000000000004</span>
<span class="mi">1424543444543424342434543444246405057484</span><span class="n">b494a48494a484b48494a4c40505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000040404000000000000000000000000040000000000000000000000000000000004040000000000000000000404040004</span>
<span class="mi">16060606060606060606060606</span><span class="n">d4e465050576d6e607070707070707070707c50505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000040400000000000000000000040000000000000000000000000000000000000400000000000000000000000004</span>
<span class="mi">15062506060606060606060606</span><span class="n">d5e566050576d7e707070707070707078507c50505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000404000000000000000404040404</span>
<span class="mi">16350606060606060606060606060666050576070707070707070707070795</span><span class="n">c50505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000040000000000000000000000040000000000000000000000000000000000040400000000000000000000000004</span>
<span class="mi">16572757470606060606060606060665050576070707070707070707079797</span><span class="n">c60505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000040404040004040400000000040000000000000000000000000000000404000000000000000000000004040404</span>
<span class="mi">15060606160606060606060606060666050575070707070707</span><span class="n">b50707070707c50505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000004040000000000000000000000000000000004</span>
<span class="mi">15060606163506060606060606060665050576070707070787</span><span class="n">b7a707070707c60505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000404000000000000000000000000000000000004</span>
<span class="mi">160606061737373706060606060606665494750785070707070707070707</span><span class="n">a5c60505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000404040400000000000000000000000000000000000404040404000000040000000000000000000004040400000000000000000000000000040404000004</span>
<span class="mi">15060606060606060606060606550606450707070707070707070707</span><span class="n">a7b7a7c60505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000404000000000000000000000000000000040400000000000000040000000000000000000404000000000000000000000000000000000000000004</span>
<span class="mi">160606060606060606063737373737663797769797</span><span class="n">a7a70707070707070707c50505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000000000040000000000000000040400000000000000000000000000000000000000000004</span>
<span class="mi">160606060606060606060606060606650505750707070707070707070795</span><span class="n">a5c50505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000404040404040000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000004040404</span>
<span class="mi">1506060606064747570606060606066505057507070707070707</span><span class="n">a7a7b787a7c50505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000404040404000000000000040000000000000000000000000000000000000000000000000000000004000004</span>
<span class="mi">16060606060666060606060625060666050576070707070707070707070707</span><span class="n">c60505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000040404040404000000000000000000000000040000000000000000000000000000000000000000000000000000040404040404</span>
<span class="mi">15060606273767060637060606060665050576070707</span><span class="n">b7a707070707078507c60505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000004040400040000000000000000000000000000000000000000000000000000040000000004</span>
<span class="mi">150606576745060606060606060635660505750707079507070707070707</span><span class="n">b5c50505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000040000000004</span>
<span class="mi">1727374757273747273727374757476705057787</span><span class="n">a787a7b797879787a797b7c70505050505050505050505050505050505050505050505050505050505050504</span>
<span class="mi">04040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404</span>
<span class="n">__gff__</span>
<span class="mi">0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001010101010101010101010101000000000100000000010100000000010000000001000000000101000000000100000000010101010101010101010101000000</span>
<span class="mi">0000000000000000000001010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="n">__map__</span>
<span class="mi">4040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000040404000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000040404000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000004040004000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000404000004040404000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000040400000004000404040000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000004040400000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000004040000000004000000040400000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000404000000000004000000000004040000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000040404000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000040400000000000004000000000000040000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000004040000000000000004000000000000000404000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4040404040404040404040000040404040404040404040404040404040404040000000000000000000000000000000000000000000004000000000000000004000000000000000004040000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000404000000000000000004000000000000000000040000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000004040400000000000000000000000000000000040000000000000000000000000000000000000000000400000000000000000004000000000000000000000400000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000040400000000000000000004000000000000000000000404000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000040404040400000000000000000000000000000000040000000000000000000000000000000000000004040000000000000000000004000000000000000000000004040000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000404040400000000000000000000000000040000000000000000000000000000000000040404000000000000000000000004000000000000000000000000040000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000404000000000000000000000000040000000000000000000000000000000004040000000000000000000000000004000000000000000000000000000400000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000040400000000000000000000000000000004000000000000000000000000000004000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000040000000000000000000000000404040000000000000000000000000000000004000000000000000000000000000004040000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000404040404040000000000000000040000000000000000000000040400000000000000000000000000000000000004000000000000000000000000000000000400000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000400000000000000000000000000040000000000000000000404040000000000000000000000000000000000000004000000000000000000000000000000000004040000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000404040400000000000000000000000000040000000000000004040400000000000000000000000000000000000000000004000000000000000000000000000000000000040400000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000400000000000000000000000000000000040000000000000004000000000000000000000000000000000000000000000004000000000000000000000000000000000000000404000000000000000000000400000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000004040404040400000000000000000000000000000000000000000000000404000000000000000000000000000000000000000000000004000000000000000000000000000000000000000004040000000000000000000000000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000040400000000000000000000000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000404000000000000000000000000000000000000000000000000000000000000000000000000000000040</span>
<span class="mi">4040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040000000000000000000404040</span>
<span class="n">__sfx__</span>
<span class="mf">00010000160001602016030160401705016050160501605016050150501305012050100500e0500</span><span class="n">c0500a05008050060400302000010000000000000000000000000000000000000000000000000000000000000</span>
<span class="mf">000100002e7102</span><span class="n">d7302d7502d7502d7502d7502c7502b7502b7502a7502775024750227501f75016750117500d750087500675005740057400473003720027100271000700007000070000700007000070000700</span>
<span class="mi">000300000</span><span class="n">d1200a140091500915009150091500815007150051500415003150021500115000150001500015000150001500015000150001500015000150001500015000140001200011000110001100011000110</span>
<span class="mi">0101000011320123201332015320163201</span><span class="n">a3201b3201e320213202232026320293202c3202e320333203432035320003000030000300003000030000300003000030000300003000030000300003000030000300</span>
<span class="mi">001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</span>
<span class="mi">010</span><span class="n">f00000003100001000310000100001000010000100001000310000100031000010000100001000010000100031000010003100001000010000100001000010000100001000010000100001000010000100001</span>
<span class="n">__music__</span>
<span class="mi">02</span> <span class="mi">0</span><span class="n">a424344</span>
</pre></div>
<p>Plutôt simple et efficace non ?</p>
<p>Amuse-toi bien et sois créatif !</p>
</div>
Créer un petit jeu de plateforme avec Löve2021-03-19T00:00:00+01:002021-03-19T00:00:00+01:00Morgantag:dotmobo.github.io,2021-03-19:/plateformer-love2d.html<p class="first last">Créer un petit jeu de plateforme avec Löve</p>
<img alt="Löve" class="align-right" src="./images/love.png" />
<p>On fait suite au <a class="reference external" href="http://dotmobo.github.io/jeux-video-lua.html">précédent article du blog</a> qui introduisait le développement de jeux vidéo en Lua avec <a class="reference external" href="https://love2d.org/">Löve</a>.
Tu vas voir comment créer un petit jeu de plateforme inspiré de <a class="reference external" href="https://store.steampowered.com/app/40800/Super_Meat_Boy/">Super Meat Boy</a> et de <a class="reference external" href="https://store.steampowered.com/app/1229580/Disc_Room/">Disc Room</a> uniquement avec Löve.</p>
<div class="section" id="disc-room-escape">
<h2>Disc Room Escape</h2>
<p>Le jeu s'appelle Disc Room Escape et <a class="reference external" href="https://dotmobo.itch.io/disc-room-escape">est jouable sur itch.io</a>.
Le but est simplement de traverser les différents niveaux, sans mourir, dans la limite de temps disponible.
Il faudra sauter sur des plateformes et éviter des scies mortelles.</p>
<p>Ici, pas besoin de librairies et outils annexes. Tout sera fait uniquement avec Löve.</p>
<img alt="Disc Room" src="./images/discroom.gif" />
</div>
<div class="section" id="code-source">
<h2>Code source</h2>
<p>Le code source est <a class="reference external" href="https://github.com/dotmobo/disc-room-escape">intégralement disponible sur Github</a>.
N'hésite pas à le récupérer, à jouer avec, à le modifier et à te l'approprier.</p>
<p>Je vais t'expliquer dans les grandes lignes son fonctionnement. Suis les liens vers les fichiers sources pour les comprendre.
Sur cet article, je ne mettrais en exemple que certains bouts de code intéressants pour éviter de trop le surcharger d'information.</p>
</div>
<div class="section" id="etat-du-jeu">
<h2>Etat du jeu</h2>
<p>On va commencer par créer un système qui gère l'état de notre jeu. Dans les prochains fichiers, on va beaucoup utiliser <strong>setmetatable</strong>,
qui permet de gérer des sortes de <a class="reference external" href="https://www.lua.org/pil/16.1.html">classes en Lua</a>.</p>
<p>Tu voudras avoir <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/StartState.lua">un écran d'accueil</a> où tu lances le jeu,
<a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/PlayState.lua">un écran avec le jeu en cours</a>,
<a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/WinState.lua">un écran de fin lors de la victoire</a> et
<a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/DeadState.lua">un écran de fin lors de la défaite</a>. Ces écrans vont utiliser les fonctions
<strong>update</strong> et <strong>draw</strong> de Löve pour se mettre à jour, afficher les éléments et gérer les touches.</p>
<div class="highlight"><pre><span></span><span class="kr">function</span> <span class="nc">mt</span><span class="p">:</span><span class="nf">update</span><span class="p">(</span><span class="n">dt</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">love</span><span class="p">.</span><span class="n">keyboard</span><span class="p">.</span><span class="n">isDown</span><span class="p">(</span><span class="s1">'return'</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">Joystick</span> <span class="ow">and</span> <span class="n">Joystick</span><span class="p">:</span><span class="n">isGamepadDown</span><span class="p">(</span><span class="s1">'start'</span><span class="p">))</span> <span class="kr">then</span>
<span class="n">GameState</span><span class="p">.</span><span class="n">setCurrent</span><span class="p">(</span><span class="s1">'Play'</span><span class="p">,</span> <span class="n">GAME_LEVEL_START</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">doorSound</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">audio</span><span class="p">.</span><span class="n">newSource</span><span class="p">(</span><span class="n">SOUND_DOOR</span><span class="p">,</span> <span class="s2">"static"</span><span class="p">)</span>
<span class="n">doorSound</span><span class="p">:</span><span class="n">play</span><span class="p">()</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nc">mt</span><span class="p">:</span><span class="nf">draw</span><span class="p">()</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">setNewFont</span><span class="p">(</span><span class="mi">12</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">setColor</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">setBackgroundColor</span><span class="p">(</span> <span class="mi">104</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span> <span class="mi">124</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span> <span class="mi">133</span><span class="o">/</span><span class="mi">255</span> <span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">img</span><span class="p">,</span> <span class="mi">70</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mf">0.4</span><span class="p">,</span> <span class="mf">0.4</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="nb">print</span><span class="p">({{</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">},</span> <span class="s1">'Press [enter] or [start] to start the game !'</span><span class="p">},</span> <span class="mi">75</span><span class="p">,</span> <span class="mi">220</span><span class="p">)</span>
<span class="kr">end</span>
</pre></div>
<p>Tous ces écrans vont être manipulables grâce à <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/GameState.lua">une classe GameState</a>
qui va se charger de gérer la transition des états. Il suffit par exemple, d'utiliser la commande suivante pour changer de niveau :</p>
<div class="highlight"><pre><span></span><span class="n">GameState</span><span class="p">.</span><span class="n">setCurrent</span><span class="p">(</span><span class="s1">'Play'</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">level_num</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
</pre></div>
<p>Le <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/main.lua">fichier principal de Löve</a> va juste se charger de démarrer le jeu sur
l'écran d'accueil et d'initier le joystick, <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/conf.lua">le fichier de configuration</a> et
<a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/const.lua">les constantes du jeu</a>.</p>
</div>
<div class="section" id="animation-et-assets">
<h2>Animation et assets</h2>
<p>Tous les assets du jeu, sons et images, sont disponibles <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/tree/master/assets">ici</a>.
Tu peux les modifier selon tes propres envies. Tu auras besoin de deux petits helpers,
<a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/Animation.lua">un pour gérer les animations</a> et
<a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/assets.lua">un autre pour gérer les assets</a>.</p>
</div>
<div class="section" id="le-monde">
<h2>Le monde</h2>
<p>Chaque élément du jeu sera ajouté à <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/World.lua">un monde</a>, qui va se charger de
vérifier en continue la position des éléments et leurs collisions.</p>
<p>On vérifie la collision de nos éléments à l'aide d'une fonction toute simple :</p>
<div class="highlight"><pre><span></span><span class="kd">local</span> <span class="kr">function</span> <span class="nf">checkCollision</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
<span class="kr">return</span> <span class="n">a</span><span class="p">.</span><span class="n">x</span> <span class="o"><</span> <span class="n">b</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">b</span><span class="p">.</span><span class="n">w</span> <span class="ow">and</span>
<span class="n">a</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">a</span><span class="p">.</span><span class="n">w</span> <span class="o">></span> <span class="n">b</span><span class="p">.</span><span class="n">x</span> <span class="ow">and</span>
<span class="n">a</span><span class="p">.</span><span class="n">y</span> <span class="o"><</span> <span class="n">b</span><span class="p">.</span><span class="n">y</span> <span class="o">+</span> <span class="n">b</span><span class="p">.</span><span class="n">h</span> <span class="ow">and</span>
<span class="n">a</span><span class="p">.</span><span class="n">h</span> <span class="o">+</span> <span class="n">a</span><span class="p">.</span><span class="n">y</span> <span class="o">></span> <span class="n">b</span><span class="p">.</span><span class="n">y</span>
<span class="kr">end</span>
</pre></div>
</div>
<div class="section" id="les-niveaux">
<h2>Les niveaux</h2>
<p>Les niveaux seront représentés par des tableaux de la manière suivante :</p>
<div class="highlight"><pre><span></span><span class="kr">return</span> <span class="p">{</span>
<span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span>
<span class="mi">10</span><span class="p">,</span><span class="mi">9</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span>
<span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span>
<span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span>
<span class="p">}</span>
</pre></div>
<p>Chaque chiffre va représenter un élément sur notre écran.</p>
<ul class="simple">
<li>le 0 représente le vide.</li>
<li>le 1 sera <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/Wall.lua">des murs</a>.</li>
<li>le 2 sera <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/Floor.lua">le sol</a>.</li>
<li>le 3 sera <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/ToggleFloor.lua">des plateformes amovibles</a>.</li>
<li>le 4 sera <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/Door.lua">les portes de sorties</a>.</li>
<li>le 5 sera <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/Disc.lua">des scies fixes</a>.</li>
<li>le 6 sera <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/Roof.lua">le toît</a>.</li>
<li>le 7 sera <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/Boss.lua">le boss du jeu</a>.</li>
<li>le 8 sera <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/Enemy.lua">les ennemies du jeu</a>, des scies qui se déplacent.</li>
<li>le 9 sera <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/Hero.lua">notre héro</a>.</li>
<li>le 10 sera un élement du décor, <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/Scaffold.lua">l'échafaudage</a>.</li>
</ul>
<p>Chacun de ces éléments utilise le système de classes de Lua via <strong>setmetatable</strong> pour sa représentation.
Une <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/Level.lua">classe Level</a> va se charger de l'affichage des éléments du niveau en fonction de ces chiffres.</p>
<p>Il sera alors possible de déclencher des évenements dans le monde via une fonction <strong>trigger</strong>, par exemple lorsque le joueur franchit une porte.</p>
<p>Pour gérer cet évenement, dans notre classe <em>Door</em> on a :</p>
<div class="highlight"><pre><span></span><span class="kr">function</span> <span class="nc">mt</span><span class="p">:</span><span class="nf">update</span><span class="p">(</span><span class="n">dt</span><span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">touches_hero</span> <span class="o">=</span> <span class="n">GameState</span><span class="p">.</span><span class="n">getCurrent</span><span class="p">().</span><span class="n">world</span><span class="p">:</span><span class="n">check</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="s1">'is_hero'</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nc">mt</span><span class="p">:</span><span class="nf">draw</span><span class="p">()</span>
<span class="n">assets</span><span class="p">.</span><span class="n">qdraw</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">y</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">.</span><span class="n">touches_hero</span> <span class="kr">then</span>
<span class="n">GameState</span><span class="p">.</span><span class="n">getCurrent</span><span class="p">():</span><span class="n">trigger</span><span class="p">(</span><span class="s1">'door:open'</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span>
</pre></div>
<p>Et dans l'état du jeu en cours <em>PlayState</em> on a :</p>
<div class="highlight"><pre><span></span><span class="kr">function</span> <span class="nc">mt</span><span class="p">:</span><span class="nf">trigger</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">actor</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">event</span> <span class="o">==</span> <span class="s1">'door:open'</span> <span class="kr">then</span>
<span class="kd">local</span> <span class="n">doorSound</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">audio</span><span class="p">.</span><span class="n">newSource</span><span class="p">(</span><span class="n">SOUND_DOOR</span><span class="p">,</span> <span class="s2">"static"</span><span class="p">)</span>
<span class="n">doorSound</span><span class="p">:</span><span class="n">play</span><span class="p">()</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">.</span><span class="n">level_num</span> <span class="o"><</span> <span class="n">GAME_LEVEL_MAX</span> <span class="kr">then</span>
<span class="n">GameState</span><span class="p">.</span><span class="n">setCurrent</span><span class="p">(</span><span class="s1">'Play'</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">level_num</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="kr">else</span>
<span class="n">GameState</span><span class="p">.</span><span class="n">setCurrent</span><span class="p">(</span><span class="s1">'Win'</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">end</span>
</pre></div>
</div>
<div class="section" id="le-hero">
<h2>Le hero</h2>
<p><a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/Hero.lua">Notre hero</a> va devoir se déplacer si on utilise le joystick ou le clavier, en utilisant un système d'accélération et de décélération dans son <strong>update</strong> :</p>
<div class="highlight"><pre><span></span><span class="kd">local</span> <span class="n">dx</span><span class="p">,</span> <span class="n">dy</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span>
<span class="kr">if</span> <span class="n">love</span><span class="p">.</span><span class="n">keyboard</span><span class="p">.</span><span class="n">isDown</span><span class="p">(</span><span class="s1">'left'</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">Joystick</span> <span class="ow">and</span> <span class="p">(</span><span class="n">Joystick</span><span class="p">:</span><span class="n">isGamepadDown</span><span class="p">(</span><span class="s1">'dpleft'</span><span class="p">)</span> <span class="ow">or</span> <span class="n">Joystick</span><span class="p">:</span><span class="n">getGamepadAxis</span><span class="p">(</span><span class="s1">'leftx'</span><span class="p">)</span> <span class="o"><=</span> <span class="o">-</span><span class="mf">0.25</span><span class="p">))</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">:</span><span class="n">setAnim</span><span class="p">(</span><span class="s1">'run'</span><span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">last_direction</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span>
<span class="c1">-- acceleration system</span>
<span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">+</span> <span class="p">(</span><span class="o">-</span><span class="n">self</span><span class="p">.</span><span class="n">speed</span> <span class="o">*</span> <span class="n">self</span><span class="p">.</span><span class="n">acceleration</span> <span class="o">*</span> <span class="n">dt</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o"><</span> <span class="o">-</span><span class="n">self</span><span class="p">.</span><span class="n">speed</span> <span class="kr">then</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">=</span> <span class="o">-</span><span class="n">self</span><span class="p">.</span><span class="n">speed</span> <span class="kr">end</span>
<span class="kr">elseif</span> <span class="n">love</span><span class="p">.</span><span class="n">keyboard</span><span class="p">.</span><span class="n">isDown</span><span class="p">(</span><span class="s1">'right'</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">Joystick</span> <span class="ow">and</span> <span class="p">(</span><span class="n">Joystick</span><span class="p">:</span><span class="n">isGamepadDown</span><span class="p">(</span><span class="s1">'dpright'</span><span class="p">)</span> <span class="ow">or</span> <span class="n">Joystick</span><span class="p">:</span><span class="n">getGamepadAxis</span><span class="p">(</span><span class="s1">'leftx'</span><span class="p">)</span> <span class="o">>=</span> <span class="mf">0.25</span><span class="p">))</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">:</span><span class="n">setAnim</span><span class="p">(</span><span class="s1">'run'</span><span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">last_direction</span> <span class="o">=</span> <span class="mi">1</span>
<span class="c1">-- acceleration system</span>
<span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">+</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">speed</span> <span class="o">*</span> <span class="n">self</span><span class="p">.</span><span class="n">acceleration</span> <span class="o">*</span> <span class="n">dt</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">></span> <span class="n">self</span><span class="p">.</span><span class="n">speed</span> <span class="kr">then</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">speed</span> <span class="kr">end</span>
<span class="kr">else</span>
<span class="c1">-- deceleration system</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o"><</span> <span class="mi">0</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">+</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">speed</span> <span class="o">*</span> <span class="n">self</span><span class="p">.</span><span class="n">deceleration</span> <span class="o">*</span> <span class="n">dt</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">></span> <span class="mi">0</span> <span class="kr">then</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">=</span> <span class="mi">0</span> <span class="kr">end</span>
<span class="kr">elseif</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">></span> <span class="mi">0</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">+</span> <span class="p">(</span><span class="o">-</span><span class="n">self</span><span class="p">.</span><span class="n">speed</span> <span class="o">*</span> <span class="n">self</span><span class="p">.</span><span class="n">deceleration</span> <span class="o">*</span> <span class="n">dt</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o"><</span> <span class="mi">0</span> <span class="kr">then</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">=</span> <span class="mi">0</span> <span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="n">dx</span> <span class="o">=</span> <span class="n">dx</span> <span class="o">+</span> <span class="n">self</span><span class="p">.</span><span class="n">vx</span> <span class="o">*</span> <span class="n">dt</span>
</pre></div>
<p>Tu va devoir gérer la gravité lors du saut dans le <strong>update</strong> également :</p>
<div class="highlight"><pre><span></span><span class="kr">if</span> <span class="p">(</span><span class="n">love</span><span class="p">.</span><span class="n">keyboard</span><span class="p">.</span><span class="n">isDown</span><span class="p">(</span><span class="s1">'up'</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">Joystick</span> <span class="ow">and</span> <span class="p">(</span><span class="n">Joystick</span><span class="p">:</span><span class="n">isGamepadDown</span><span class="p">(</span><span class="s1">'a'</span><span class="p">))))</span> <span class="kr">then</span>
<span class="c1">-- init jump</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">:</span><span class="n">canJump</span><span class="p">()</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">.</span><span class="n">vy</span> <span class="o">=</span> <span class="n">HERO_JUMP_SPEED</span>
<span class="n">self</span><span class="p">.</span><span class="n">is_jumping</span> <span class="o">=</span> <span class="kc">true</span>
<span class="kd">local</span> <span class="n">jumpSound</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">audio</span><span class="p">.</span><span class="n">newSource</span><span class="p">(</span><span class="n">SOUND_JUMP</span><span class="p">,</span> <span class="s2">"static"</span><span class="p">)</span>
<span class="n">jumpSound</span><span class="p">:</span><span class="n">play</span><span class="p">()</span>
<span class="c1">-- during the jump</span>
<span class="kr">elseif</span> <span class="n">self</span><span class="p">.</span><span class="n">is_jumping</span> <span class="o">==</span> <span class="kc">true</span> <span class="kr">then</span>
<span class="c1">-- reduce the gravity for smooth jump</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">.</span><span class="n">vy</span> <span class="o"><</span> <span class="mi">0</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">.</span><span class="n">vy</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">vy</span> <span class="o">-</span> <span class="n">HERO_JUMP_GRAVITY</span> <span class="o">*</span> <span class="n">dt</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="c1">-- gravity</span>
<span class="kr">if</span> <span class="n">self</span><span class="p">:</span><span class="n">isGrounded</span><span class="p">()</span> <span class="kr">then</span>
<span class="n">self</span><span class="p">.</span><span class="n">vy</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">self</span><span class="p">.</span><span class="n">is_jumping</span> <span class="o">=</span> <span class="kc">false</span>
<span class="n">self</span><span class="p">.</span><span class="n">ungroundedTime</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kr">else</span>
<span class="n">self</span><span class="p">:</span><span class="n">setAnim</span><span class="p">(</span><span class="s1">'jump'</span><span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">vy</span> <span class="o">=</span> <span class="nb">math.min</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">vy</span> <span class="o">+</span> <span class="n">HERO_GRAVITY</span> <span class="o">*</span> <span class="n">dt</span><span class="p">,</span> <span class="n">HERO_MAX_VELOCITY</span><span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">ungroundedTime</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">ungroundedTime</span> <span class="o">+</span> <span class="n">dt</span>
<span class="kr">end</span>
</pre></div>
<p>Et bien évidemment, il faudra l'animer à l'aide de notre helper :</p>
<div class="highlight"><pre><span></span><span class="n">self</span><span class="p">:</span><span class="n">setAnim</span><span class="p">(</span><span class="s1">'run'</span><span class="p">)</span>
</pre></div>
<p>et enfin le déplacer dans notre monde via :</p>
<div class="highlight"><pre><span></span><span class="n">GameState</span><span class="p">.</span><span class="n">getCurrent</span><span class="p">().</span><span class="n">world</span><span class="p">:</span><span class="n">move</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">dx</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">y</span> <span class="o">+</span> <span class="n">self</span><span class="p">.</span><span class="n">vy</span><span class="p">,</span> <span class="s1">'is_solid'</span><span class="p">)</span>
</pre></div>
</div>
<div class="section" id="les-particules-de-sang">
<h2>Les particules de sang</h2>
<p>Enfin, à la mort, on va utiliser <a class="reference external" href="https://github.com/dotmobo/disc-room-escape/blob/master/Particles.lua">un système de particules</a> pour gérer le sang.
On va pouvoir utiliser différents paramètres pour styliser nos particules :</p>
<div class="highlight"><pre><span></span><span class="n">p</span><span class="p">.</span><span class="n">psystem</span><span class="p">:</span><span class="n">setParticleLifetime</span><span class="p">(</span><span class="mf">0.5</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">psystem</span><span class="p">:</span><span class="n">setEmissionRate</span><span class="p">(</span><span class="mi">128</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">psystem</span><span class="p">:</span><span class="n">setEmitterLifetime</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">psystem</span><span class="p">:</span><span class="n">setSizeVariation</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">psystem</span><span class="p">:</span><span class="n">setLinearAcceleration</span><span class="p">(</span><span class="o">-</span><span class="mi">100</span><span class="p">,</span> <span class="o">-</span><span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">100</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">psystem</span><span class="p">:</span><span class="n">setColors</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</pre></div>
</div>
<div class="section" id="conclusion">
<h2>Conclusion</h2>
<p>J'espère que cet aperçu va te donner envie d'essayer Löve plus en profondeur !</p>
<p>Ici, on a tout fait à la main, sans librairie. C'est la meilleur manière de procéder je pense pour avoir le contrôle complet de ton code.</p>
<p>Mais si tu veux voir ce que ça donne en utilisant des librairies de gestion de
collisions comme <a class="reference external" href="https://github.com/kikito/bump.lua">Bump</a>, des utilitaires pour gérer l'état du jeu ou la caméra comme <a class="reference external" href="https://github.com/HDictus/hump">Hump</a>,
l'outil de gestion des animations <a class="reference external" href="https://github.com/kikito/anim8">Anim8</a>, ou encore l'utilitaire <a class="reference external" href="https://github.com/karai17/Simple-Tiled-Implementation">STI</a>
pour manipuler des niveaux créés avec <a class="reference external" href="https://www.mapeditor.org/">Tiled</a>, tu peux jeter un oeil à mon deuxième projet Löve, <a class="reference external" href="https://dotmobo.itch.io/the-legend-of-shifu">The Legend Of Shifu</a>,
dont l'intégralité du code source est <a class="reference external" href="https://github.com/dotmobo/the-legend-of-shifu">disponible sur Github</a>. C'est un petit jeu inspiré de
<a class="reference external" href="https://store.steampowered.com/app/250900/The_Binding_of_Isaac_Rebirth/">The Binding Of Isaac</a>.</p>
<img alt="The Legend Of Shifu" src="./images/shifu.png" />
<p>Have fun !</p>
</div>
Débuter le développement de jeux vidéo en créant un Infinite Runner en Lua2021-03-16T00:00:00+01:002021-03-16T00:00:00+01:00Morgantag:dotmobo.github.io,2021-03-16:/jeux-video-lua.html<p class="first last">Débuter le développement de jeux vidéo en créant un Infinite Runner en Lua</p>
<img alt="Lua" class="align-right" src="./images/lua-logo.gif" />
<p>L'an dernier, principalement pendant la période de confinement, je me suis mis dans l'idée d'expérimenter le développement de jeux vidéo.
C'est un domaine qui m'a toujours attiré et passionné depuis tout petit, mais je n'ai jamais vraiment pris le temps de m'y attarder sérieusement.</p>
<p>Les moteurs de jeux actuels comme l'Unreal Engine ou Unity ne m'attiraient pas vraiment. Ils me donnaient l'impression de devoir apprendre à utiliser un logiciel de
création complexe plutôt que de développer simplement un jeu.</p>
<p>L'idée était de rester au plus simple, au plus rapide et au plus prêt du code. Et donc de pouvoir utiliser le combo Linux + VS Code pour le développement.</p>
<p>Afin de trouver un moteur de jeu qui convenait à mes attentes, je me suis intéressé à l'excellent site <a class="reference external" href="https://itch.io/">Itch.io</a>, qui permet aux développeurs
indépendants de vendre leurs créations et de participer à des <em>Game Jams</em>. Pour mes lecteurs musiciens, c'est un peu le <a class="reference external" href="https://bandcamp.com/">Bandcamp</a> des jeux vidéo.</p>
<p>Et ce qui est intéressant, c'est qu'il propose une <a class="reference external" href="https://itch.io/game-development/engines/most-projects">liste des moteurs de jeux les plus utilisés</a> sur sa plateforme.
En éliminant les logiciels de création (Unity, Construct, Game Maker, Unreal Engine, Godot), il nous reste principalement deux moteurs de jeux :</p>
<p><a class="reference external" href="https://love2d.org/">LÖVE</a> et <a class="reference external" href="https://www.lexaloffle.com/pico-8.php">PICO-8</a></p>
<p>Ces deux moteurs sont vraiment excellents et on l'avantage d'être basé sur le même langage de programmation, <a class="reference external" href="http://www.lua.org/">Lua</a>.</p>
<div class="section" id="lua-1">
<h2>Lua</h2>
<p>Lua est un langage de script écrit en C très simple à apprendre et à utiliser. Il permet de faire de la programmation impérative de base, et se situe donc à mi-chemin entre
Python, Go et Shellscript. Il ne faut pas espérer y trouver des concepts très avancés en programmation objet ou fonctionnel.</p>
<p>Néanmoins, il est parfait pour s'initier au développement de jeux vidéo sans avoir de grandes connaissances en programmation en amont.</p>
<p>Ses concepts de base sont bien résumés sur <a class="reference external" href="https://learnxinyminutes.com/docs/fr-fr/lua-fr/">Learn Lua in Y Minutes</a> et ça ressemble à ça :</p>
<div class="highlight"><pre><span></span><span class="c1">-- defines a factorial function</span>
<span class="kr">function</span> <span class="nf">fact</span> <span class="p">(</span><span class="n">n</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">0</span> <span class="kr">then</span>
<span class="kr">return</span> <span class="mi">1</span>
<span class="kr">else</span>
<span class="kr">return</span> <span class="n">n</span> <span class="o">*</span> <span class="n">fact</span><span class="p">(</span><span class="n">n</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"enter a number:"</span><span class="p">)</span>
<span class="n">a</span> <span class="o">=</span> <span class="nb">io.read</span><span class="p">(</span><span class="s2">"*number"</span><span class="p">)</span> <span class="c1">-- read a number</span>
<span class="nb">print</span><span class="p">(</span><span class="n">fact</span><span class="p">(</span><span class="n">a</span><span class="p">))</span>
</pre></div>
<p>Prends-toi 10 minutes pour te familiariser avec le langage et on peut passer à la suite !</p>
</div>
<div class="section" id="love-1">
<h2>LÖVE</h2>
<img alt="Löve" class="align-right" src="./images/love.png" />
<p>On commence par le moteur LÖVE, qui est un moteur open source, à l'inverse de PICO-8 qui est payant et propriétaire.
Tu l'installes et tu fais le <a class="reference external" href="https://love2d.org/wiki/Getting_Started">Getting Started</a> du site officiel qui va te permettre que vérifier que tout est ok.</p>
<p>Tout le fonctionnement de LÖVE se situe autour de 3 fonctions principales.</p>
<p>La fonction <strong>load</strong> permet d'initialiser les éléments du jeu une fois au démarrage, comme le chargement d'images ou l'initialisation de variables.
Dans l'exemple ci-dessous, on charge une image et on définit la police de caractères et les couleurs de notre jeu :</p>
<div class="highlight"><pre><span></span><span class="kr">function</span> <span class="nc">love</span><span class="p">.</span><span class="nf">load</span><span class="p">()</span>
<span class="n">image</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newImage</span><span class="p">(</span><span class="s2">"cake.jpg"</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">setNewFont</span><span class="p">(</span><span class="mi">12</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">setColor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">setBackgroundColor</span><span class="p">(</span><span class="mi">255</span><span class="p">,</span><span class="mi">255</span><span class="p">,</span><span class="mi">255</span><span class="p">)</span>
<span class="kr">end</span>
</pre></div>
<p>La fonction <strong>update</strong> est appelée continuellement, comme une boucle infinie, et utilise le <strong>delta time</strong> en paramètre qui représente le temps écoulé entre chaque appel.
C'est ici que l'on va mettre à jour les données de notre jeu. Tu vas pouvoir l'utiliser pour mettre à jour les positions X et Y de ton joueur en fonction de l'appuie
sur une touche par exemple. Ici, on incrémente un nombre lorsque l'on appuie sur <em>flèche haut</em> :</p>
<div class="highlight"><pre><span></span><span class="kr">function</span> <span class="nc">love</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="n">dt</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">love</span><span class="p">.</span><span class="n">keyboard</span><span class="p">.</span><span class="n">isDown</span><span class="p">(</span><span class="s2">"up"</span><span class="p">)</span> <span class="kr">then</span>
<span class="n">num</span> <span class="o">=</span> <span class="n">num</span> <span class="o">+</span> <span class="mi">100</span> <span class="o">*</span> <span class="n">dt</span> <span class="c1">-- this would increment num by 100 per second</span>
<span class="kr">end</span>
<span class="kr">end</span>
</pre></div>
<p>La fonction <strong>draw</strong> gère l'affichage des éléments, et les met automatiquement à jour lorsque des modifications sont apportées sur la position du joueur dans <strong>update</strong>
par exemple. La séparation de <strong>update</strong> et de <strong>draw</strong> permet d'éviter des soucis de performances. Si des méthodes de calculs prennent beaucoup de temps dans <strong>update</strong>,
ça n'affecte pas l'affichage des éléments dans <strong>draw</strong> et évite des effets de ralentissements.
Ici, on affiche une image en fonction de sa position et du texte.</p>
<div class="highlight"><pre><span></span><span class="kr">function</span> <span class="nc">love</span><span class="p">.</span><span class="nf">draw</span><span class="p">()</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">image</span><span class="p">,</span> <span class="n">imgx</span><span class="p">,</span> <span class="n">imgy</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="nb">print</span><span class="p">(</span><span class="s2">"Click and drag the cake around or use the arrow keys"</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
<span class="kr">end</span>
</pre></div>
</div>
<div class="section" id="outils">
<h2>Outils</h2>
<p>Avant de s'attaquer au développement de ton premier jeu, il faut t'équiper de certains outils.</p>
<p><strong>Pixel Art</strong></p>
<p>Pour l'instant, on ne s'intéressera qu'aux dessins sous forme de <em>Pixel Art</em>. Tu peux utiliser les outils suivants :</p>
<ul class="simple">
<li><a class="reference external" href="https://www.piskelapp.com/">Piskel</a> : Outil en ligne pour faire tes dessins et animations. Parfait pour débuter.</li>
<li><a class="reference external" href="https://krita.org/">Krita</a> : Outil de dessin très populaire sous Linux, qui permet d'utiliser <a class="reference external" href="https://docs.krita.org/en/reference_manual/brushes/brush_engines/pixel_brush_engine.html">une brosse spécifique pour le Pixel Art</a>.</li>
<li><a class="reference external" href="https://www.aseprite.org/">Aseprite</a> : Mon outil préféré, mais payant pour avoir un installateur. Il est néanmoins open source et peut être compilé gratuitement depuis les sources sur Github.</li>
</ul>
<p><strong>Musique</strong></p>
<p>Au niveau du son, tu peux t'équiper des outils suivants :</p>
<ul class="simple">
<li><a class="reference external" href="https://freesound.org/">Freesound</a> : Librarie en ligne de milliers de sons utilisable gratuitement. Parfait pour trouver des musiques et des effets.</li>
<li><a class="reference external" href="https://audacity.fr/">Audacity</a> : Outil d'édition de fichier audio, simple et efficace si tu as besoin de faire des retouches ou d'enregistrer des sons.</li>
<li><a class="reference external" href="https://jfxr.frozenfractal.com/">Jfxr</a> : Successeur de <a class="reference external" href="https://www.bfxr.net/">Bfxr</a>, c'est un outil qui permet de générer aléatoirement des sons 8bits comme le saut, le tir au laser et autres.</li>
</ul>
<p><strong>Editeur de cartes</strong></p>
<p>On ne l'utilisera pas tout suite, mais le meilleur outil pour éditer des niveaux est <a class="reference external" href="https://www.mapeditor.org/">Tiled</a>. Il est gratuit, très populaire et est
compatible avec la plupart des moteurs de jeux. Tu peux déjà y jeter un oeil si ça t'intéresse.</p>
</div>
<div class="section" id="runner">
<h2>Runner</h2>
<p>Ma première expérience dans l'utilisation de LÖVE a été la création de ce petit <em>Infinite Runner</em> appelé <a class="reference external" href="https://dotmobo.itch.io/multipla-adventure">Multipla Adventure</a>.
Il s'agissait d'un projet inspiré par la destruction du carter d'huile ma Fiat Multipla sur un rocher dans un chemin forestier !</p>
<img alt="Multipla" src="./images/multipla.png" />
<p>L'intégralité du code source du jeu est disponible <a class="reference external" href="https://github.com/dotmobo/runner-game">ici</a>.
Tu peux récupérer le dossier <strong>images</strong> et <strong>sounds</strong>, où utiliser les outils cités plus haut pour faire tes propres créations !
N'hésite pas à prendre du temps pour comprendre les différents fichiers du jeu.</p>
<p>On va avoir besoin de quelques petites fonctions utilitaires pour gérer les animations et les collisions. Dans un fichier <strong>utils.lua</strong>, tu mets :</p>
<div class="highlight"><pre><span></span><span class="kr">function</span> <span class="nf">newAnimation</span><span class="p">(</span><span class="n">image</span><span class="p">,</span> <span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">,</span> <span class="n">duration</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">animation</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">animation</span><span class="p">.</span><span class="n">spriteSheet</span> <span class="o">=</span> <span class="n">image</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">quads</span> <span class="o">=</span> <span class="p">{};</span>
<span class="kr">for</span> <span class="n">y</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">image</span><span class="p">:</span><span class="n">getHeight</span><span class="p">()</span> <span class="o">-</span> <span class="n">height</span><span class="p">,</span> <span class="n">height</span> <span class="kr">do</span>
<span class="kr">for</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">image</span><span class="p">:</span><span class="n">getWidth</span><span class="p">()</span> <span class="o">-</span> <span class="n">width</span><span class="p">,</span> <span class="n">width</span> <span class="kr">do</span>
<span class="nb">table.insert</span><span class="p">(</span><span class="n">animation</span><span class="p">.</span><span class="n">quads</span><span class="p">,</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newQuad</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">width</span><span class="p">,</span> <span class="n">height</span><span class="p">,</span> <span class="n">image</span><span class="p">:</span><span class="n">getDimensions</span><span class="p">()))</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="n">animation</span><span class="p">.</span><span class="n">duration</span> <span class="o">=</span> <span class="n">duration</span> <span class="ow">or</span> <span class="mi">1</span>
<span class="n">animation</span><span class="p">.</span><span class="n">currentTime</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kr">return</span> <span class="n">animation</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">setScale</span><span class="p">()</span>
<span class="kd">local</span> <span class="n">scale</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">getWidth</span><span class="p">()</span> <span class="o">/</span> <span class="n">WIN_WIDTH</span>
<span class="kd">local</span> <span class="n">scaleY</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">getHeight</span><span class="p">()</span> <span class="o">/</span> <span class="n">WIN_HEIGHT</span>
<span class="kr">if</span> <span class="n">scaleY</span> <span class="o"><</span> <span class="n">scale</span> <span class="kr">then</span> <span class="n">scale</span> <span class="o">=</span> <span class="n">scaleY</span> <span class="kr">end</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">scale</span><span class="p">(</span><span class="n">scale</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">collideRect</span><span class="p">(</span><span class="n">rect1</span><span class="p">,</span> <span class="n">rect2</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">rect1</span><span class="p">.</span><span class="n">x</span> <span class="o"><</span> <span class="n">rect2</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">rect2</span><span class="p">.</span><span class="n">width</span> <span class="ow">and</span>
<span class="n">rect1</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">rect1</span><span class="p">.</span><span class="n">width</span> <span class="o">></span> <span class="n">rect2</span><span class="p">.</span><span class="n">x</span> <span class="ow">and</span>
<span class="n">rect1</span><span class="p">.</span><span class="n">y</span> <span class="o"><</span> <span class="n">rect2</span><span class="p">.</span><span class="n">y</span> <span class="o">+</span> <span class="n">rect2</span><span class="p">.</span><span class="n">height</span> <span class="ow">and</span>
<span class="n">rect1</span><span class="p">.</span><span class="n">height</span> <span class="o">+</span> <span class="n">rect1</span><span class="p">.</span><span class="n">y</span> <span class="o">></span> <span class="n">rect2</span><span class="p">.</span><span class="n">y</span> <span class="kr">then</span>
<span class="kr">return</span> <span class="kc">true</span>
<span class="kr">end</span>
<span class="kr">return</span> <span class="kc">false</span>
<span class="kr">end</span>
</pre></div>
<p>Notre joueur sera donc une voiture qui restera fixe à gauche de l'écran. La voiture pourra sauter avec la barre d'espace ou en appuyant
sur l'écran de notre smartphone android. Ce qui signifie qu'il faudra gérer la gravité à minima.
Il faudra également gérer l'animation et les sons de la voiture.</p>
<p>Tu crées donc un fichier <strong>player.lua</strong> comme ci-dessous :</p>
<div class="highlight"><pre><span></span><span class="kd">local</span> <span class="n">player</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">player</span><span class="p">.</span><span class="n">width</span> <span class="o">=</span> <span class="mi">48</span>
<span class="n">player</span><span class="p">.</span><span class="n">height</span> <span class="o">=</span> <span class="mi">48</span>
<span class="n">player</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">72</span>
<span class="n">player</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">WIN_HEIGHT</span><span class="o">-</span><span class="n">player</span><span class="p">.</span><span class="n">height</span><span class="o">-</span><span class="mi">20</span>
<span class="n">player</span><span class="p">.</span><span class="n">speedY</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">player</span><span class="p">.</span><span class="n">gravity</span> <span class="o">=</span> <span class="o">-</span><span class="mi">500</span>
<span class="n">player</span><span class="p">.</span><span class="n">jumpHeight</span> <span class="o">=</span> <span class="o">-</span><span class="mi">250</span>
<span class="n">player</span><span class="p">.</span><span class="n">groundY</span> <span class="o">=</span> <span class="n">WIN_HEIGHT</span><span class="o">-</span><span class="mi">48</span><span class="o">-</span><span class="mi">20</span>
<span class="n">player</span><span class="p">.</span><span class="n">alive</span> <span class="o">=</span> <span class="kc">false</span>
<span class="kr">function</span> <span class="nf">loadPlayer</span><span class="p">()</span>
<span class="n">player</span><span class="p">.</span><span class="n">img</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newImage</span><span class="p">(</span><span class="s2">"images/car.png"</span><span class="p">)</span>
<span class="n">player</span><span class="p">.</span><span class="n">img</span><span class="p">:</span><span class="n">setFilter</span><span class="p">(</span><span class="s2">"nearest"</span><span class="p">,</span><span class="s2">"nearest"</span><span class="p">)</span>
<span class="n">player</span><span class="p">.</span><span class="n">anim</span> <span class="o">=</span> <span class="n">newAnimation</span><span class="p">(</span><span class="n">player</span><span class="p">.</span><span class="n">img</span><span class="p">,</span> <span class="n">player</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">player</span><span class="p">.</span><span class="n">height</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">player</span><span class="p">.</span><span class="n">jumpSound</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">audio</span><span class="p">.</span><span class="n">newSource</span><span class="p">(</span><span class="s2">"sounds/340629__mickyman5000__chainsaw-stop .wav"</span><span class="p">,</span> <span class="s2">"static"</span><span class="p">)</span>
<span class="kr">return</span> <span class="n">player</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">updatePlayer</span><span class="p">(</span><span class="n">dt</span><span class="p">)</span>
<span class="n">player</span><span class="p">.</span><span class="n">anim</span><span class="p">.</span><span class="n">currentTime</span> <span class="o">=</span> <span class="n">player</span><span class="p">.</span><span class="n">anim</span><span class="p">.</span><span class="n">currentTime</span> <span class="o">+</span> <span class="n">dt</span><span class="o">*</span><span class="mi">10</span>
<span class="c1">-- android touch</span>
<span class="kd">local</span> <span class="n">touches</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">touch</span><span class="p">.</span><span class="n">getTouches</span><span class="p">()</span>
<span class="kr">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">touch</span> <span class="kr">in</span> <span class="nb">ipairs</span><span class="p">(</span><span class="n">touches</span><span class="p">)</span> <span class="kr">do</span>
<span class="kd">local</span> <span class="n">tx</span><span class="p">,</span> <span class="n">ty</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">touch</span><span class="p">.</span><span class="n">getPosition</span><span class="p">(</span><span class="n">touch</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">player</span><span class="p">.</span><span class="n">speedY</span> <span class="o">==</span> <span class="mi">0</span> <span class="kr">then</span>
<span class="n">player</span><span class="p">.</span><span class="n">speedY</span> <span class="o">=</span> <span class="n">player</span><span class="p">.</span><span class="n">jumpHeight</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="c1">-- keyboard</span>
<span class="kr">if</span> <span class="n">love</span><span class="p">.</span><span class="n">keyboard</span><span class="p">.</span><span class="n">isDown</span><span class="p">(</span><span class="s1">'space'</span><span class="p">)</span> <span class="kr">then</span>
<span class="kr">if</span> <span class="n">player</span><span class="p">.</span><span class="n">speedY</span> <span class="o">==</span> <span class="mi">0</span> <span class="kr">then</span>
<span class="n">player</span><span class="p">.</span><span class="n">jumpSound</span><span class="p">:</span><span class="n">play</span><span class="p">()</span>
<span class="n">player</span><span class="p">.</span><span class="n">speedY</span> <span class="o">=</span> <span class="n">player</span><span class="p">.</span><span class="n">jumpHeight</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="c1">-- jumping</span>
<span class="kr">if</span> <span class="n">player</span><span class="p">.</span><span class="n">speedY</span> <span class="o">~=</span> <span class="mi">0</span> <span class="kr">then</span>
<span class="n">player</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">player</span><span class="p">.</span><span class="n">y</span> <span class="o">+</span> <span class="n">player</span><span class="p">.</span><span class="n">speedY</span> <span class="o">*</span> <span class="n">dt</span>
<span class="n">player</span><span class="p">.</span><span class="n">speedY</span> <span class="o">=</span> <span class="n">player</span><span class="p">.</span><span class="n">speedY</span> <span class="o">-</span> <span class="n">player</span><span class="p">.</span><span class="n">gravity</span> <span class="o">*</span> <span class="n">dt</span>
<span class="kr">end</span>
<span class="c1">-- stop jumping</span>
<span class="kr">if</span> <span class="n">player</span><span class="p">.</span><span class="n">y</span> <span class="o">></span> <span class="n">player</span><span class="p">.</span><span class="n">groundY</span> <span class="kr">then</span>
<span class="n">player</span><span class="p">.</span><span class="n">speedY</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">player</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">player</span><span class="p">.</span><span class="n">groundY</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">drawPlayer</span><span class="p">()</span>
<span class="kd">local</span> <span class="n">spriteNum</span> <span class="o">=</span> <span class="nb">math.floor</span><span class="p">(</span><span class="n">player</span><span class="p">.</span><span class="n">anim</span><span class="p">.</span><span class="n">currentTime</span> <span class="o">%</span> <span class="o">#</span><span class="n">player</span><span class="p">.</span><span class="n">anim</span><span class="p">.</span><span class="n">quads</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">player</span><span class="p">.</span><span class="n">anim</span><span class="p">.</span><span class="n">spriteSheet</span><span class="p">,</span> <span class="n">player</span><span class="p">.</span><span class="n">anim</span><span class="p">.</span><span class="n">quads</span><span class="p">[</span><span class="n">spriteNum</span><span class="p">],</span> <span class="n">player</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">player</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">resetPlayer</span><span class="p">()</span>
<span class="n">player</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">72</span>
<span class="n">player</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">WIN_HEIGHT</span><span class="o">-</span><span class="n">player</span><span class="p">.</span><span class="n">height</span><span class="o">-</span><span class="mi">20</span>
<span class="n">player</span><span class="p">.</span><span class="n">alive</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">player</span><span class="p">.</span><span class="n">speedY</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kr">end</span>
</pre></div>
<p>La voiture restant fixe, c'est le décor qui va bouger pour donner une impression de mouvement. Pour donner du relief, on va créer 3 strates de montagnes
et les faire défiler à des vitesses différentes.</p>
<p>Tu crées un fichier <strong>landscape.lua</strong> comme qui suit :</p>
<div class="highlight"><pre><span></span><span class="kd">local</span> <span class="n">imgMoutainsBack</span>
<span class="kd">local</span> <span class="n">moutainsBackX</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kd">local</span> <span class="n">imgMoutainsFront</span>
<span class="kd">local</span> <span class="n">moutainsFrontX</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kd">local</span> <span class="n">imgTrees</span>
<span class="kd">local</span> <span class="n">treesX</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kd">local</span> <span class="n">ground</span>
<span class="n">moutainsBack</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newQuad</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="p">,</span><span class="n">WIN_HEIGHT</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="p">,</span><span class="n">WIN_HEIGHT</span><span class="p">)</span>
<span class="n">moutainsFront</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newQuad</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="p">,</span><span class="n">WIN_HEIGHT</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="p">,</span><span class="n">WIN_HEIGHT</span><span class="p">)</span>
<span class="n">trees</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newQuad</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="p">,</span><span class="n">WIN_HEIGHT</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="p">,</span><span class="n">WIN_HEIGHT</span><span class="p">)</span>
<span class="n">ground</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newQuad</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="p">,</span><span class="n">WIN_HEIGHT</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="p">,</span><span class="n">WIN_HEIGHT</span><span class="p">)</span>
<span class="kr">function</span> <span class="nf">loadLandscape</span><span class="p">()</span>
<span class="n">imgMoutainsBack</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newImage</span><span class="p">(</span><span class="s2">"images/mountains_back.png"</span><span class="p">)</span>
<span class="n">imgMoutainsBack</span><span class="p">:</span><span class="n">setFilter</span><span class="p">(</span><span class="s2">"nearest"</span><span class="p">,</span><span class="s2">"nearest"</span><span class="p">)</span>
<span class="n">imgMoutainsFront</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newImage</span><span class="p">(</span><span class="s2">"images/mountains_front.png"</span><span class="p">)</span>
<span class="n">imgMoutainsFront</span><span class="p">:</span><span class="n">setFilter</span><span class="p">(</span><span class="s2">"nearest"</span><span class="p">,</span><span class="s2">"nearest"</span><span class="p">)</span>
<span class="n">imgTrees</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newImage</span><span class="p">(</span><span class="s2">"images/trees.png"</span><span class="p">)</span>
<span class="n">imgTrees</span><span class="p">:</span><span class="n">setFilter</span><span class="p">(</span><span class="s2">"nearest"</span><span class="p">,</span><span class="s2">"nearest"</span><span class="p">)</span>
<span class="n">imgGround</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newImage</span><span class="p">(</span><span class="s2">"images/ground.png"</span><span class="p">)</span>
<span class="n">imgGround</span><span class="p">:</span><span class="n">setFilter</span><span class="p">(</span><span class="s2">"nearest"</span><span class="p">,</span><span class="s2">"nearest"</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">updateLandscape</span><span class="p">(</span><span class="n">dt</span><span class="p">)</span>
<span class="n">moutainsBackX</span> <span class="o">=</span> <span class="p">(</span><span class="n">moutainsBackX</span> <span class="o">+</span> <span class="mi">30</span><span class="o">*</span><span class="n">dt</span><span class="p">)</span> <span class="o">%</span> <span class="n">WIN_WIDTH</span>
<span class="n">moutainsFrontX</span> <span class="o">=</span> <span class="p">(</span><span class="n">moutainsFrontX</span> <span class="o">+</span> <span class="mi">60</span><span class="o">*</span><span class="n">dt</span><span class="p">)</span> <span class="o">%</span> <span class="n">WIN_WIDTH</span>
<span class="n">treesX</span> <span class="o">=</span> <span class="p">(</span><span class="n">treesX</span> <span class="o">+</span> <span class="mi">180</span><span class="o">*</span><span class="n">dt</span><span class="p">)</span> <span class="o">%</span> <span class="n">WIN_WIDTH</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">drawLandscape</span><span class="p">()</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">imgMoutainsBack</span><span class="p">,</span><span class="n">moutainsBack</span><span class="p">,</span><span class="mi">0</span><span class="o">-</span><span class="n">moutainsBackX</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">imgMoutainsBack</span><span class="p">,</span><span class="n">moutainsBack</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="o">-</span><span class="n">moutainsBackX</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">imgMoutainsFront</span><span class="p">,</span><span class="n">moutainsFront</span><span class="p">,</span><span class="mi">0</span><span class="o">-</span><span class="n">moutainsFrontX</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">imgMoutainsFront</span><span class="p">,</span><span class="n">moutainsFront</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="o">-</span><span class="n">moutainsFrontX</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">imgTrees</span><span class="p">,</span><span class="n">trees</span><span class="p">,</span><span class="mi">0</span><span class="o">-</span><span class="n">treesX</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">imgTrees</span><span class="p">,</span><span class="n">trees</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="o">-</span><span class="n">treesX</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">imgGround</span><span class="p">,</span><span class="n">ground</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="kr">end</span>
</pre></div>
<p>Enfin, on va gérer l'apparition aléatoire de notre rocher se déplacant à des vitesses différentes, que le joueur devra éviter.
Dans un fichier <strong>enemy.lua</strong>, tu écris :</p>
<div class="highlight"><pre><span></span><span class="kd">local</span> <span class="n">enemy</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">width</span> <span class="o">=</span> <span class="mi">36</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">height</span> <span class="o">=</span> <span class="mi">36</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">groundY</span> <span class="o">=</span> <span class="mi">20</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">WIN_WIDTH</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">WIN_HEIGHT</span><span class="o">-</span><span class="n">enemy</span><span class="p">.</span><span class="n">height</span><span class="o">-</span><span class="n">enemy</span><span class="p">.</span><span class="n">groundY</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">speedX</span> <span class="o">=</span> <span class="mi">200</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">extraX</span> <span class="o">=</span> <span class="mi">200</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">speedXMin</span> <span class="o">=</span> <span class="mi">200</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">speedXMax</span> <span class="o">=</span> <span class="mi">700</span>
<span class="kr">function</span> <span class="nf">loadEnemy</span><span class="p">()</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">quad</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newQuad</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">enemy</span><span class="p">.</span><span class="n">width</span><span class="p">,</span><span class="n">enemy</span><span class="p">.</span><span class="n">height</span><span class="p">,</span><span class="n">enemy</span><span class="p">.</span><span class="n">width</span><span class="p">,</span><span class="n">enemy</span><span class="p">.</span><span class="n">height</span><span class="p">)</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">img</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newImage</span><span class="p">(</span><span class="s2">"images/enemy1.png"</span><span class="p">)</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">img</span><span class="p">:</span><span class="n">setFilter</span><span class="p">(</span><span class="s2">"nearest"</span><span class="p">,</span><span class="s2">"nearest"</span><span class="p">)</span>
<span class="kr">return</span> <span class="n">enemy</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">updateEnemy</span><span class="p">(</span><span class="n">dt</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">enemy</span><span class="p">.</span><span class="n">x</span> <span class="o">></span> <span class="n">WIN_WIDTH</span> <span class="kr">then</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">speedX</span> <span class="o">=</span> <span class="nb">math.random</span><span class="p">(</span><span class="n">enemy</span><span class="p">.</span><span class="n">speedXMin</span><span class="p">,</span> <span class="n">enemy</span><span class="p">.</span><span class="n">speedXMax</span><span class="p">)</span>
<span class="kr">end</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="p">(</span><span class="n">enemy</span><span class="p">.</span><span class="n">x</span> <span class="o">-</span> <span class="n">enemy</span><span class="p">.</span><span class="n">speedX</span><span class="o">*</span><span class="n">dt</span><span class="p">)</span> <span class="o">%</span> <span class="p">(</span><span class="n">WIN_WIDTH</span> <span class="o">+</span> <span class="n">enemy</span><span class="p">.</span><span class="n">extraX</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">drawEnemy</span><span class="p">()</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">draw</span><span class="p">(</span><span class="n">enemy</span><span class="p">.</span><span class="n">img</span><span class="p">,</span><span class="n">enemy</span><span class="p">.</span><span class="n">quad</span><span class="p">,</span><span class="n">enemy</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="n">enemy</span><span class="p">.</span><span class="n">y</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nf">resetEnemy</span><span class="p">()</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">WIN_WIDTH</span>
<span class="n">enemy</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">WIN_HEIGHT</span><span class="o">-</span><span class="n">enemy</span><span class="p">.</span><span class="n">height</span><span class="o">-</span><span class="n">enemy</span><span class="p">.</span><span class="n">groundY</span>
<span class="kr">end</span>
</pre></div>
<p>Il ne reste plus qu'à inclure tous ces éléments dans LÖVE !</p>
<p>Pour ce faire, tu crées un fichier de constantes <strong>const.lua</strong> pour définir la taille de la fenêtre et le titre du jeu :</p>
<div class="highlight"><pre><span></span><span class="n">TITLE</span> <span class="o">=</span> <span class="s2">"Game 1"</span>
<span class="n">PATH_ICON</span> <span class="o">=</span> <span class="s2">"images/enemy1.png"</span>
<span class="n">WIN_WIDTH</span> <span class="o">=</span> <span class="mi">640</span>
<span class="n">WIN_HEIGHT</span> <span class="o">=</span> <span class="mi">360</span>
</pre></div>
<p>Puis un fichier de configuration <strong>conf.lua</strong> :</p>
<div class="highlight"><pre><span></span><span class="nb">require</span><span class="p">(</span><span class="s1">'const'</span><span class="p">)</span>
<span class="kr">function</span> <span class="nc">love</span><span class="p">.</span><span class="nf">conf</span><span class="p">(</span><span class="n">t</span><span class="p">)</span>
<span class="n">t</span><span class="p">.</span><span class="n">window</span><span class="p">.</span><span class="n">title</span> <span class="o">=</span> <span class="n">TITLE</span> <span class="c1">-- Change le titre de la fenêtre</span>
<span class="n">t</span><span class="p">.</span><span class="n">window</span><span class="p">.</span><span class="n">icon</span> <span class="o">=</span> <span class="n">PATH_ICON</span> <span class="c1">-- Change l'icone de la fenêtre</span>
<span class="n">t</span><span class="p">.</span><span class="n">window</span><span class="p">.</span><span class="n">width</span> <span class="o">=</span> <span class="n">WIN_WIDTH</span> <span class="c1">-- Change la largeur de la fenêtre</span>
<span class="n">t</span><span class="p">.</span><span class="n">window</span><span class="p">.</span><span class="n">height</span> <span class="o">=</span> <span class="n">WIN_HEIGHT</span> <span class="c1">-- Change la hauteur de la fenêtre</span>
<span class="n">t</span><span class="p">.</span><span class="n">console</span> <span class="o">=</span> <span class="kc">false</span>
<span class="kr">end</span>
</pre></div>
<p>Enfin, on intègre tout ça dans la boucle de LÖVE à l'aide des méthodes <strong>load</strong>, <strong>update</strong> et <strong>draw</strong> :</p>
<div class="highlight"><pre><span></span><span class="nb">require</span><span class="p">(</span><span class="s1">'utils'</span><span class="p">)</span>
<span class="nb">require</span><span class="p">(</span><span class="s1">'landscape'</span><span class="p">)</span>
<span class="nb">require</span><span class="p">(</span><span class="s1">'player'</span><span class="p">)</span>
<span class="nb">require</span><span class="p">(</span><span class="s1">'enemy'</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">player</span>
<span class="kd">local</span> <span class="n">enemy</span>
<span class="kd">local</span> <span class="n">score</span>
<span class="kr">function</span> <span class="nf">reset</span><span class="p">()</span>
<span class="n">score</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">resetPlayer</span><span class="p">()</span>
<span class="n">resetEnemy</span><span class="p">()</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nc">love</span><span class="p">.</span><span class="nf">load</span><span class="p">()</span>
<span class="c1">-- font</span>
<span class="n">font</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">newFont</span><span class="p">(</span><span class="mi">18</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">setFont</span><span class="p">(</span><span class="n">font</span><span class="p">)</span>
<span class="c1">-- game</span>
<span class="n">loadLandscape</span><span class="p">()</span>
<span class="n">player</span> <span class="o">=</span> <span class="n">loadPlayer</span><span class="p">()</span>
<span class="n">enemy</span> <span class="o">=</span> <span class="n">loadEnemy</span><span class="p">()</span>
<span class="n">crashSound</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">audio</span><span class="p">.</span><span class="n">newSource</span><span class="p">(</span><span class="s2">"sounds/151624__qubodup__clank-car-crash-collision.wav"</span><span class="p">,</span> <span class="s2">"static"</span><span class="p">)</span>
<span class="n">music</span> <span class="o">=</span> <span class="n">love</span><span class="p">.</span><span class="n">audio</span><span class="p">.</span><span class="n">newSource</span><span class="p">(</span><span class="s2">"sounds/514960__deleted-user-11009121__synthwave-loop-100bpm.mp3"</span><span class="p">,</span> <span class="s2">"static"</span><span class="p">)</span>
<span class="n">music</span><span class="p">:</span><span class="n">setLooping</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
<span class="n">music</span><span class="p">:</span><span class="n">play</span><span class="p">()</span>
<span class="n">music</span><span class="p">:</span><span class="n">setVolume</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
<span class="n">score</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nc">love</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="n">dt</span><span class="p">)</span>
<span class="n">updateLandscape</span><span class="p">(</span><span class="n">dt</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">player</span><span class="p">.</span><span class="n">alive</span> <span class="o">==</span> <span class="kc">true</span> <span class="kr">then</span>
<span class="n">updatePlayer</span><span class="p">(</span><span class="n">dt</span><span class="p">)</span>
<span class="n">updateEnemy</span><span class="p">(</span><span class="n">dt</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">collideRect</span><span class="p">(</span><span class="n">player</span><span class="p">,</span> <span class="n">enemy</span><span class="p">)</span> <span class="kr">then</span>
<span class="n">crashSound</span><span class="p">:</span><span class="n">play</span><span class="p">()</span>
<span class="n">player</span><span class="p">.</span><span class="n">alive</span> <span class="o">=</span> <span class="kc">false</span>
<span class="kr">end</span>
<span class="n">score</span> <span class="o">=</span> <span class="n">score</span> <span class="o">+</span> <span class="mi">20</span><span class="o">*</span><span class="n">dt</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nc">love</span><span class="p">.</span><span class="nf">draw</span><span class="p">()</span>
<span class="n">setScale</span><span class="p">()</span>
<span class="n">drawLandscape</span><span class="p">()</span>
<span class="kr">if</span> <span class="n">player</span><span class="p">.</span><span class="n">alive</span> <span class="o">==</span> <span class="kc">true</span> <span class="kr">then</span>
<span class="n">drawPlayer</span><span class="p">()</span>
<span class="n">drawEnemy</span><span class="p">()</span>
<span class="c1">-- score</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="nb">print</span><span class="p">({{</span><span class="mi">2</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span><span class="mi">9</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span><span class="mi">4</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span><span class="mi">1</span><span class="p">},</span> <span class="s1">'score: '</span><span class="o">..</span><span class="nb">math.floor</span><span class="p">(</span><span class="n">score</span><span class="p">)},</span><span class="mi">8</span><span class="p">,</span><span class="mi">8</span><span class="p">)</span>
<span class="kr">else</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">printf</span><span class="p">({{</span><span class="mi">2</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span><span class="mi">9</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span><span class="mi">4</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span><span class="mi">1</span><span class="p">},</span> <span class="s1">'Multipla Adventure'</span><span class="p">},</span><span class="mi">0</span><span class="p">,</span><span class="n">WIN_HEIGHT</span><span class="o">/</span><span class="mi">3</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="p">,</span><span class="s2">"center"</span><span class="p">)</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">printf</span><span class="p">({{</span><span class="mi">2</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span><span class="mi">9</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span><span class="mi">4</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span><span class="mi">1</span><span class="p">},</span> <span class="s2">"Press space to play and jump "</span><span class="p">},</span><span class="mi">0</span><span class="p">,</span><span class="n">WIN_HEIGHT</span><span class="o">/</span><span class="mi">3</span><span class="o">+</span><span class="mi">32</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="p">,</span><span class="s2">"center"</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">score</span> <span class="o">~=</span> <span class="mi">0</span> <span class="kr">then</span>
<span class="n">love</span><span class="p">.</span><span class="n">graphics</span><span class="p">.</span><span class="n">printf</span><span class="p">({{</span><span class="mi">2</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span><span class="mi">9</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span><span class="mi">4</span><span class="o">/</span><span class="mi">255</span><span class="p">,</span><span class="mi">1</span><span class="p">},</span> <span class="s2">"your score: "</span><span class="o">..</span> <span class="nb">math.floor</span><span class="p">(</span><span class="n">score</span><span class="p">)},</span><span class="mi">0</span><span class="p">,</span><span class="n">WIN_HEIGHT</span><span class="o">/</span><span class="mi">3</span><span class="o">+</span><span class="mi">64</span><span class="p">,</span><span class="n">WIN_WIDTH</span><span class="p">,</span><span class="s2">"center"</span><span class="p">)</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nc">love</span><span class="p">.</span><span class="nf">keyreleased</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
<span class="kr">if</span> <span class="n">player</span><span class="p">.</span><span class="n">alive</span> <span class="o">==</span> <span class="kc">false</span> <span class="kr">then</span>
<span class="n">reset</span><span class="p">()</span>
<span class="kr">end</span>
<span class="kr">if</span> <span class="n">key</span> <span class="o">==</span> <span class="s2">"escape"</span> <span class="kr">then</span>
<span class="n">love</span><span class="p">.</span><span class="n">event</span><span class="p">.</span><span class="n">quit</span><span class="p">()</span>
<span class="kr">end</span>
<span class="kr">end</span>
<span class="kr">function</span> <span class="nc">love</span><span class="p">.</span><span class="nf">touchreleased</span><span class="p">(</span> <span class="n">id</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">dx</span><span class="p">,</span> <span class="n">dy</span><span class="p">,</span> <span class="n">pressure</span> <span class="p">)</span>
<span class="kr">if</span> <span class="n">player</span><span class="p">.</span><span class="n">alive</span> <span class="o">==</span> <span class="kc">false</span> <span class="kr">then</span>
<span class="n">reset</span><span class="p">()</span>
<span class="kr">end</span>
<span class="kr">end</span>
</pre></div>
<p>Il ne reste plus qu'à exécuter LÖVE depuis le dossier du jeu pour voir le résultat :</p>
<div class="highlight"><pre><span></span>love .
</pre></div>
<p>Happy coding !</p>
</div>
Man vs. Wild: Angular2020-05-21T00:00:00+02:002020-05-21T00:00:00+02:00Morgantag:dotmobo.github.io,2020-05-21:/man-vs-wild-angular.html<p class="first last">Man vs. Wild: Angular</p>
<img alt="Angular" class="align-right" src="./images/angular.png" />
<p>Ah <a class="reference external" href="https://angular.io/">Angular</a>... Sujet de débat éternel pour tous les trolls des technos front. Ce framework est soit détesté, soit adulé,
mais on est rarement dans la nuance. Pour le coup je botte un peu en touche, car mes sentiments à l'égard d'Angular font office de montagnes russes.
Et après m'être battu pendant 2 ans avec ce framework pour un projet pro, j'avais envie de te faire un petit retour des choses que j'aurai aimé savoir dès le début
pour m'en sortir avec cette stack et ses concepts.</p>
<div class="section" id="editeur">
<h2>Editeur</h2>
<p>De ce que j'ai pu tester, l'éditeur le plus simple à prendre en main pour Angular (et de loin) est <a class="reference external" href="https://code.visualstudio.com/">VS Code</a>.
J'ai un peu testé Intellij, Atom et Vim, mais je suis très vite retourné sur VS Code.</p>
<div class="section" id="plugins">
<h3>Plugins</h3>
<p>Au niveau des plugins, mes quelques indispensables sont:</p>
<ul class="simple">
<li><a class="reference external" href="https://marketplace.visualstudio.com/items?itemName=Mikael.Angular-BeastCode">Angular 8 Snippets</a>: Snippets pour angular, material et ngrx.</li>
<li><a class="reference external" href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode">Prettier</a>: Formatage automatique du code.</li>
<li><a class="reference external" href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-tslint-plugin">Tsint</a>: Linter typescript.</li>
<li><a class="reference external" href="https://marketplace.visualstudio.com/items?itemName=CoenraadS.bracket-pair-colorizer">Bracket Pair Colorizer</a> : Pour s'en sortir avec les parenthèses !</li>
</ul>
</div>
</div>
<div class="section" id="rxjs">
<h2>Rxjs</h2>
<p>On attaque directement avec le plus important. <a class="reference external" href="https://rxjs-dev.firebaseapp.com/">Rxjs</a> est une libraire de <strong>reactive programming</strong> permettant de travailler avec des
flux de données asynchrones. Il faut savoir que Angular repose complètement sur cette librairie et qu'il est absolument vital de la maîtriser.
Toute la technicité du code va en grande partie dépendre de Rxjs.</p>
<div class="section" id="operateurs">
<h3>Opérateurs</h3>
<p>Il faut absolument en <strong>maitriser les principaux opérateurs</strong> comme <em>switchMap</em>, <em>combineLatest</em>, <em>filter</em>, <em>debounce</em> et autres.
Si ce n'est pas déjà fait, commence par aller sur <a class="reference external" href="https://www.learnrxjs.io">learnrxjs.io</a> et fait le tour des opérateurs, joue avec, essaye de les comprendre et de les garder en tête.</p>
</div>
<div class="section" id="subscribe-unsubscribe">
<h3>Subscribe/unsubscribe</h3>
<p>Une des premières grosses sources de bugs est <strong>d'oublier de se désabonner d'un flux</strong>. Ça entraine des fuites de mémoires et ça produit des effets de bord en cascade.
Tu risques de te retrouver abonné à certains flux plusieurs fois inutilement et d'avoir des comportements étranges sans comprendre facilement d'où ça vient.</p>
<p>Si possible, évite d'utiliser le <em>unsubscribe</em> à la main et privilégie plutôt le déclenchement d'un signal de fin de flux, avec <strong>first()</strong> par exemple.
Ici on récupère la première valeur du flux <em>source</em> et un signal de fin de flux est alors émis grace au <em>first()</em>, ce qui nous désabonne directement sans utiliser <em>unsubscribe</em>:</p>
<div class="highlight"><pre><span></span><span class="nx">source</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">first</span><span class="p">()).</span><span class="nx">subscribe</span><span class="p">(</span><span class="nx">val</span> <span class="p">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="sb">`First value: </span><span class="si">${</span><span class="nx">val</span><span class="si">}</span><span class="sb">`</span><span class="p">));</span>
</pre></div>
<p>En second lieu, tu peux aussi privilégier l'abonnement directement dans le template via <strong>monflux$ | async</strong>, car le désabonnement est alors géré automatiquement
par Angular lorsque l'on quitte le composant:</p>
<div class="highlight"><pre><span></span><span class="p"><</span><span class="nt">span</span><span class="p">></span>Hello {{ myName$ | async }}<span class="p"></</span><span class="nt">span</span><span class="p">></span>
</pre></div>
<p>Enfin, si tu es obligé de rester abonné à un flux dans ton fichier typescript pour x raisons, n'oublie surtout pas de te désabonner dans l'événement <strong>ngOnDestroy</strong>:</p>
<div class="highlight"><pre><span></span><span class="err">@</span><span class="nx">Component</span><span class="p">({...})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">AppComponent</span> <span class="kr">implements</span> <span class="nx">OnInit</span><span class="p">,</span> <span class="nx">OnDestroy</span> <span class="p">{</span>
<span class="nx">subscription</span><span class="o">:</span> <span class="nx">Subscription</span>
<span class="nx">ngOnInit</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">timer$</span> <span class="o">=</span> <span class="nx">Rx</span><span class="p">.</span><span class="nx">Observable</span><span class="p">.</span><span class="nx">interval</span><span class="p">(</span><span class="mf">500</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">subscription</span> <span class="o">=</span> <span class="nx">timer$</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">(</span><span class="nx">x</span> <span class="p">=></span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">x</span><span class="p">));</span>
<span class="p">}</span>
<span class="nx">ngOnDestroy</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">subscription</span><span class="p">.</span><span class="nx">unsubscribe</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Dernier point, il faut également <strong>éviter de faire des subscribe dans des subscribe</strong> car c'est pas très propre et ça peut entrainer des comportements non voulus.</p>
</div>
<div class="section" id="marble-tests">
<h3>Marble tests</h3>
<p>Il est possible de faire des tests unitaires plutôt poussés de tes flux avec des <strong>marble tests</strong>.
<a class="reference external" href="https://rxjs-dev.firebaseapp.com/guide/testing/marble-testing">La documentation officielle</a> est pas trop mal faite,
mais il faut savoir que c'est pas forcément évident à appréhender.</p>
<p>Pour info ca ressemble à ça:</p>
<div class="highlight"><pre><span></span><span class="k">import</span> <span class="p">{</span> <span class="nx">TestScheduler</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">'rxjs/testing'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">testScheduler</span> <span class="o">=</span> <span class="ow">new</span> <span class="nx">TestScheduler</span><span class="p">((</span><span class="nx">actual</span><span class="p">,</span> <span class="nx">expected</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span>
<span class="c1">// asserting the two objects are equal</span>
<span class="c1">// e.g. using chai.</span>
<span class="nx">expect</span><span class="p">(</span><span class="nx">actual</span><span class="p">).</span><span class="nx">deep</span><span class="p">.</span><span class="nx">equal</span><span class="p">(</span><span class="nx">expected</span><span class="p">);</span>
<span class="p">});</span>
<span class="c1">// This test will actually run *synchronously*</span>
<span class="nx">it</span><span class="p">(</span><span class="s1">'generate the stream correctly'</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span>
<span class="nx">testScheduler</span><span class="p">.</span><span class="nx">run</span><span class="p">(</span><span class="nx">helpers</span> <span class="p">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">cold</span><span class="p">,</span> <span class="nx">expectObservable</span><span class="p">,</span> <span class="nx">expectSubscriptions</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">helpers</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">e1</span> <span class="o">=</span> <span class="nx">cold</span><span class="p">(</span><span class="s1">'-a--b--c---|'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">subs</span> <span class="o">=</span> <span class="s1">'^----------!'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="s1">'-a-----c---|'</span><span class="p">;</span>
<span class="nx">expectObservable</span><span class="p">(</span><span class="nx">e1</span><span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">throttleTime</span><span class="p">(</span><span class="mf">3</span><span class="p">,</span> <span class="nx">testScheduler</span><span class="p">))).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">expected</span><span class="p">);</span>
<span class="nx">expectSubscriptions</span><span class="p">(</span><span class="nx">e1</span><span class="p">.</span><span class="nx">subscriptions</span><span class="p">).</span><span class="nx">toBe</span><span class="p">(</span><span class="nx">subs</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
</pre></div>
</div>
<div class="section" id="resolver-avec-du-cache">
<h3>Resolver avec du cache</h3>
<p>En Angular, les <em>resolvers</em> sont des services qui permettent de récupérer des données d'une api avant d'afficher une page.
Il est intéressant de savoir qu'il est possible de gérer facilement du cache avec l'opérateur <strong>shareReplay</strong> de rxjs de cette manière:</p>
<div class="highlight"><pre><span></span><span class="k">import</span> <span class="p">{</span> <span class="nx">Injectable</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">'@angular/core'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Resolve</span><span class="p">,</span> <span class="nx">ActivatedRouteSnapshot</span><span class="p">,</span> <span class="nx">RouterStateSnapshot</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">'@angular/router'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Observable</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">'rxjs'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Country</span><span class="p">,</span> <span class="nx">NomenclatureService</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">'ins-common-lib'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">shareReplay</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">'rxjs/operators'</span><span class="p">;</span>
<span class="err">@</span><span class="nx">Injectable</span><span class="p">({</span>
<span class="nx">providedIn</span><span class="o">:</span> <span class="s1">'root'</span>
<span class="p">})</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">PaysResolver</span> <span class="kr">implements</span> <span class="nx">Resolve</span><span class="o"><</span><span class="nx">Country</span><span class="p">[]</span><span class="o">></span> <span class="p">{</span>
<span class="kr">private</span> <span class="nx">pays$</span><span class="o">:</span> <span class="nx">Observable</span><span class="o"><</span><span class="nx">Country</span><span class="p">[]</span><span class="o">></span><span class="p">;</span>
<span class="kr">constructor</span><span class="p">(</span><span class="kr">private</span> <span class="nx">service</span><span class="o">:</span> <span class="nx">NomenclatureService</span><span class="p">)</span> <span class="p">{}</span>
<span class="nx">resolve</span><span class="p">(</span><span class="nx">_route</span><span class="o">:</span> <span class="nx">ActivatedRouteSnapshot</span><span class="p">,</span> <span class="nx">_state</span><span class="o">:</span> <span class="nx">RouterStateSnapshot</span><span class="p">)</span><span class="o">:</span> <span class="nx">Observable</span><span class="o"><</span><span class="nx">Country</span><span class="p">[]</span><span class="o">></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">pays$</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">pays$</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">service</span><span class="p">.</span><span class="nx">listCountries</span><span class="p">().</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">shareReplay</span><span class="p">(</span><span class="mf">1</span><span class="p">,</span> <span class="mf">3600000</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">pays$</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Ici, le <em>resolver</em> récupère la première requête et la met en cache pendant 1h. Pratique pour éviter de faire des appels systématiques à l'api !</p>
</div>
</div>
<div class="section" id="store">
<h2>Store</h2>
<p>L'utilisation d'un <strong>store type Redux</strong> pour Angular est très intéressant pour les gros projets.
Ça permet de débugger plus facilement l'application et ça facilite l'intéraction entre plusieurs pages en partageant des données.
Le <em>store</em> le plus utilisé par la communauté est <a class="reference external" href="https://ngrx.io/">Ngrx</a>, donc je t'invite à partir sur celui-là si tu n'a pas de préférences particulières.
Potasse un peu la <a class="reference external" href="https://ngrx.io/guide/store">doc officielle</a> pour en comprendre le fonctionnement.
Après rxjs, c'est la deuxième librairie qu'il est vital de maîtriser.</p>
<div class="section" id="debug">
<h3>Debug</h3>
<p>L'outil de debug indispensable est le <a class="reference external" href="https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=fr">Redux Devtools</a>.
Ça va te permettre de visualiser ton <em>store</em> en direct et de lancer des actions manuellement.</p>
</div>
<div class="section" id="facade">
<h3>Facade</h3>
<p>Plutôt que de manipuler directement le <em>store</em> dans les composants, je te conseille d'utiliser un service Angular qui va s'en charger.
Dans ce service, tu vas y mettre tes appels aux sélecteurs, l'éxecution de tes actions et autres. C'est une manière d'utiliser le <strong>design pattern façade</strong>.
Tu peux jeter un oeil à <a class="reference external" href="https://medium.com/@thomasburlesonIA/ngrx-facades-better-state-management-82a04b9a1e39">cet article</a> où c'est bien expliqué.
Concrètement ta façade va ressembler à ça :</p>
<div class="highlight"><pre><span></span><span class="err">@</span><span class="nx">Injectable</span><span class="p">()</span>
<span class="k">export</span> <span class="kd">class</span> <span class="nx">CarsFacade</span> <span class="p">{</span>
<span class="nx">loaded$</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nx">select</span><span class="p">(</span><span class="nx">carsQuery</span><span class="p">.</span><span class="nx">getIsLoaded</span><span class="p">);</span>
<span class="nx">allCars$</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nx">select</span><span class="p">(</span><span class="nx">carsQuery</span><span class="p">.</span><span class="nx">getAllCars</span><span class="p">);</span>
<span class="nx">selectedCar$</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nx">select</span><span class="p">(</span><span class="nx">carsQuery</span><span class="p">.</span><span class="nx">getSelectedCar</span><span class="p">);</span>
<span class="kr">constructor</span><span class="p">(</span><span class="kr">private</span> <span class="nx">store</span><span class="o">:</span> <span class="nx">Store</span><span class="o"><</span><span class="nx">CarsState</span><span class="o">></span><span class="p">)</span> <span class="p">{}</span>
<span class="nx">loadAllCars</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="ow">new</span> <span class="nx">LoadCar</span><span class="p">());</span>
<span class="p">}</span>
<span class="nx">selectCar</span><span class="p">(</span><span class="nx">carId</span><span class="o">:</span> <span class="nx">string</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nx">dispatch</span><span class="p">(</span><span class="ow">new</span> <span class="nx">SelectCar</span><span class="p">(</span><span class="nx">carId</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>Et c'est donc ce service que tu vas injecter dans tes composants plutôt que le <em>store</em> directement !
Ça permet de garder des composants plus lisibles, et d'isoler la partie <em>store</em>. Si un jour tu migres de technos de <em>store</em>, il te suffira de modifier les façades.</p>
</div>
<div class="section" id="formulaires">
<h3>Formulaires</h3>
<p>Il est possible de gérer nos formulaires Angular directement dans le <em>store ngrx</em>.
C'est vraiment très pratique si tu dois faire une application avec beaucoup de <strong>formulaires complexes</strong>. Ça facilite grandement le debug des formulaires
et on a une manière propre de les créer. Cette librairie s'appelle <a class="reference external" href="https://ngrx-forms.readthedocs.io/">ngrx-forms</a> et est vraiment au top !</p>
</div>
<div class="section" id="error-thrown">
<h3>Error thrown</h3>
<p>J'en ai fait des cauchemars de celle-là ! Si tu fais des tests unitaires pour ton application Angular, tu risques de la rencontrer souvent.
Elle peut survenir aléatoirement et c'est dur à débugguer, une horreur. Mais en gros, après m'être arraché les cheveux, si tu rencontre un <strong>Error thrown</strong>
à l'exécution de tes tests unitaires, c'est qu'il te manque dans 99% des cas <strong>l'importation et l'initialisation de données de ton store</strong> quelque part dans un des tests.</p>
</div>
</div>
<div class="section" id="ui">
<h2>UI</h2>
<p>Niveau UI, tu peux partir sur la librairie de composants <a class="reference external" href="https://material.angular.io/">Angular Material</a>.
Elle a l'avantage d'être open source et d'être officiellement supportée par Angular.</p>
<div class="section" id="material">
<h3>Material</h3>
<p>Quand tu as des doutes sur l'utilisation de tes composants <strong>material</strong>, n'hésite pas à te référer à la <a class="reference external" href="https://material.io/">documentation officielle de google sur material</a>, c'est une mine d'or.</p>
</div>
<div class="section" id="theme">
<h3>Theme</h3>
<p>Il est possible de <a class="reference external" href="https://material.angular.io/guide/theming#defining-a-custom-theme">faire ton propre theme</a>.
Et de la même manière, tu peux également <a class="reference external" href="https://material.angular.io/guide/theming-your-components">themer tes propres composants customisés</a>.</p>
</div>
</div>
<div class="section" id="angular-1">
<h2>Angular</h2>
<p>Allez on y est presque, place aux tips sur les mécaniques internes de Angular.</p>
<div class="section" id="i18n">
<h3>i18n</h3>
<p>Avec le module i18n de Angular, il est possible d'internationaliser les templates.
Oui... mais il faut savoir qu'il <strong>ne permet pas d'internationaliser les chaines de caractères en dehors des templates</strong> !
C'est une demande qui est <a class="reference external" href="https://github.com/angular/angular/issues/11405">ouverte depuis 4 ans</a> et qui n'a toujours pas été résolue.
Donc il faut soit prendre son mal en patience, soit utiliser une librairie externe pour l'internationalisation, ou alors gérer ça dans le code à la main.</p>
<p>Franchement, en comparaison d'autres frameworks, l'internationalisation dans Angular n'est vraiment pas terrible. Et en plus il faut se taper des fichiers xml ...</p>
</div>
<div class="section" id="schematics">
<h3>Schematics</h3>
<p>Les <em>schematics</em> sont les templates utilisés par <em>angular cli</em> pour générer les composants, les modules et autres.
C'est pratique à utiliser, par contre c'est une horreur à écrire. Franchement, <strong>ne perds pas de temps à en créer</strong>, car ça ne te servira pas à grand chose au final.</p>
</div>
<div class="section" id="lazy-loading">
<h3>Lazy loading</h3>
<p>Pour gagner en performance, il faut savoir qu'il est possible de découper son application en plusieurs <a class="reference external" href="https://angular.io/guide/lazy-loading-ngmodules">sous-modules lazy loadés</a>.
Ça permet à l'application de répondre rapidement lorsque l'on va sur la page d'accueil, et de charger les modules nécessaires que lorsqu'ils sont vraiment demandés par l'utilisateur.
Il suffit de déclarer nos modules de cette manière dans le fichier de <em>routing</em> :</p>
<div class="highlight"><pre><span></span><span class="kd">const</span> <span class="nx">routes</span><span class="o">:</span> <span class="nx">Routes</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">{</span>
<span class="nx">path</span><span class="o">:</span> <span class="s1">'items'</span><span class="p">,</span>
<span class="nx">loadChildren</span><span class="o">:</span> <span class="p">()</span> <span class="p">=></span> <span class="k">import</span><span class="p">(</span><span class="s1">'./items/items.module'</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">m</span> <span class="p">=></span> <span class="nx">m</span><span class="p">.</span><span class="nx">ItemsModule</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">];</span>
</pre></div>
</div>
<div class="section" id="guards-vs-resolvers">
<h3>Guards vs Resolvers</h3>
<p>La différence entre les deux concepts n'est pas toujours bien comprise, donc on va juste faire un petit point ici:</p>
<ul class="simple">
<li>Les <em>Guards</em> sont utilisés <strong>pour autoriser ou non l'utilisateur à accéder à une page</strong>. On va y faire des contrôles sur l'authentification et sur les autorisations.</li>
<li>Les <em>Resolvers</em> sont utilisés pour <strong>récupérer des données d'une api</strong> nécessaire au bon fonctionnement d'un page, comme des nomenclatures ou autres.</li>
</ul>
<p>Evite donc d'utiliser les <em>guards</em> pour récupérer des données qui n'ont rien à voir avec les autorisations !</p>
</div>
<div class="section" id="detection-des-changements">
<h3>Detection des changements</h3>
<p>Si tu rencontre des soucis au niveau de la detection des changements, verifie que tu n'as pas une stratégie particulière type <strong>ChangeDetectionStrategy.OnPush</strong> sur ton composant.
Cette stratégie permet d'avoir de meilleurs performances mais risque de ne pas détecter les changements sur les variables en dehors des <em>inputs</em>.
Donc pour des petits composants qui fonctionnent uniquement à base de <em>input/output</em> c'est parfait, mais pour le reste il faut se méfier.</p>
</div>
<div class="section" id="librairies-et-workspaces">
<h3>Librairies et workspaces</h3>
<p>Il y a quelques temps encore, il fallait installer et gérer <em>ng-packagr</em> à la main pour créer des libraires.
Aujourd'hui, c'est directement disponible via <em>angular cli</em>. Voici les commandes pour créer, tester, builder et publier une librairie :</p>
<div class="highlight"><pre><span></span>ng new my-workspace --create-application<span class="o">=</span><span class="nb">false</span>
<span class="nb">cd</span> my-workspace
ng generate library my-lib
ng build my-lib
ng <span class="nb">test</span> my-lib
ng lint my-lib
ng build my-lib --prod
<span class="nb">cd</span> dist/my-lib
npm publish
</pre></div>
<p>Il est également possible de créer un workspace qui va contenir plusieurs applications et librairies pour un même projet.</p>
<div class="highlight"><pre><span></span>ng new my-workspace --createApplication<span class="o">=</span><span class="s2">"false"</span>
<span class="nb">cd</span> my-workspace
ng generate library my-first-lib
ng generate library my-second-lib
ng generate application my-first-app
ng generate application my-second-app
</pre></div>
<p>C'est pratique dans le cas où tu aurais <strong>plusieurs applications qui partagent des librairies communes</strong>.
Par exemple, on pourrait imaginer une application pour les clients et une pour l'administration du site avec des modèles métier en commun dans une librarie partagée.</p>
</div>
<div class="section" id="mot-de-la-fin">
<h3>Mot de la fin</h3>
<p>Et voilà, on a plus ou moins fait le tour de quelques tips que j'aurai bien aimé savoir en début de projet pour gagner du temps.
Si un de ces points a pu t'aider, n'hésite pas à en faire part dans les commentaires. Bon courage !</p>
</div>
</div>
Les killer features d'Elixir2018-08-03T00:00:00+02:002018-08-03T00:00:00+02:00Morgantag:dotmobo.github.io,2018-08-03:/elixir-killer-features.html<p class="first last">Les killer features d'Elixir</p>
<img alt="Elixir" class="align-right" src="./images/elixir.png" />
<p>Ça fait un petit moment que j'ai dans l'idée de bosser un langage fonctionnel, histoire d'améliorer mes compétences de dev
en m'entrainant à réfléchir autrement. <em>Haskell</em> m'attirait beaucoup, car il semblait représenter la perfection du paradigme fonctionnel.
Mais bon, il faut vraiment prendre le temps de s'y investir, et il n'est pas simple de l'utiliser rapidement pour nos petits <em>use-cases</em> quotidiens.</p>
<p>Quand aux langages de type <em>Lisp</em>, c'est intéressant et rigolo mais utiliser des milliers de parenthèses partout, très peu pour moi (ok je sais, c'est cliché mais c'est comme ça).</p>
<p>C'est alors que plusieurs langages dits modernes fîrent leur apparition, comme <em>Go</em>, <em>Rust</em>, <em>Elixir</em> et <em>Crystal</em>.
Et j'ai dans mon entourage des devs qui ont fait le cheminement suivant :</p>
<p><em>Python -> Go -> Elixir</em></p>
<p>Après avoir entendu beaucoup de bien d'<a class="reference external" href="https://elixir-lang.org/">Elixir</a>, j'ai décidé de m'y mettre et je ne suis pour l'instant pas déçu !</p>
<p>Du coup, j'avais envie de te montrer les quelques <em>killer features</em> d'<em>Elixir</em> pour un dev qui vient principalement du monde <em>Python</em> à la base.</p>
<p>Pour la petite histoire, c'est un langage inventé par un ancien dev de <em>Ruby on Rails</em>, donc on a droit une syntaxe toute mimi.
Et ça tourne sur la VM <em>Erlang</em>, qui est réputée comme étant très performante en programmation concurrente.</p>
<div class="section" id="comprehension">
<h2>Compréhension</h2>
<p>Alléluia, on a les listes et dictionnaires compréhensions. Comme tout <em>pythoneux</em> qui se respecte, ça fait plaisir de voir ça ici !</p>
<p>En <em>Python</em>, on écrirait par exemple une petite liste compréhension de cette manière :</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">myList</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="o">*</span> <span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span> <span class="k">if</span> <span class="n">x</span> <span class="o">></span> <span class="mi">2</span><span class="p">]</span>
<span class="o">>>></span> <span class="n">myList</span>
<span class="p">[</span><span class="mi">9</span><span class="p">,</span> <span class="mi">16</span><span class="p">]</span>
</pre></div>
<p>En <em>Elixir</em>, ça donne ça :</p>
<div class="highlight"><pre><span></span><span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="n">myList</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="mi">1</span><span class="o">..</span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">x</span><span class="w"></span>
<span class="p">[</span><span class="mi">9</span><span class="p">,</span><span class="w"> </span><span class="mi">16</span><span class="p">]</span><span class="w"></span>
</pre></div>
<p>Et pour faire un dictionnaire compréhension à partir d'une liste, en <em>Python</em> on ferait ça :</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">myDict</span> <span class="o">=</span> <span class="p">{</span><span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="o">**</span><span class="mi">2</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span> <span class="k">if</span> <span class="n">x</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">}</span>
<span class="o">>>></span> <span class="n">myDict</span>
<span class="p">{</span><span class="mi">2</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">4</span><span class="p">:</span> <span class="mi">16</span><span class="p">}</span>
</pre></div>
<p>En <em>Elixir</em>, ça donne ça :</p>
<div class="highlight"><pre><span></span><span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="n">myDict</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="mi">1</span><span class="o">..</span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="n">rem</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="ss">into</span><span class="p">:</span><span class="w"> </span><span class="p">%{},</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">x</span><span class="p">}</span><span class="w"></span>
<span class="p">%{</span><span class="mi">2</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="mi">16</span><span class="p">}</span><span class="w"></span>
</pre></div>
<p>Tu remarqueras qu'<em>Elixir</em> utilise le typage dynamique comme en <em>Python</em>, du coup on peut avoir un syntaxe assez concise.</p>
</div>
<div class="section" id="fonction-anonyme">
<h2>Fonction anonyme</h2>
<p>Dans <em>Elixir</em>, on a droit aux fonctions anonymes, qui sont beaucoup plus poussées que les lambdas de <em>Python</em>.</p>
<p>En <em>Python</em>:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">add</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span>
<span class="o">>>></span> <span class="n">add</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
<span class="mi">7</span>
</pre></div>
<p>En <em>Elixir</em> :</p>
<div class="highlight"><pre><span></span><span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">fn</span><span class="w"> </span><span class="n">a</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="k">end</span><span class="w"></span>
<span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="n">add</span><span class="o">.</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">)</span><span class="w"></span>
<span class="mi">7</span><span class="w"></span>
</pre></div>
<p>Ici on a un exemple relativement simple, mais sache que tu n'as pas de limitation de syntaxe dans les fonctions anonymes d'<em>Elixir</em>.</p>
<p>Il s'agit également de <em>closures</em>, donc elles ont accès aux variables du <em>scope</em>. Tu remarqueras la syntaxe <strong>.(</strong> pour l'appel de la fonction.
C'est une volonté d'<em>Elixir</em> de différencier les appels des fonctions anonymes par rapport au appels des fonctions normales.</p>
</div>
<div class="section" id="pattern-matching">
<h2>Pattern matching</h2>
<p>Sûrement le plus gros point fort de ce langage, ce qui le rend unique. Le symbole <strong>=</strong> ne sert non pas à assigner une variable, mais à faire du <em>pattern matching</em>.</p>
<p>En effet, pour faire simple, en <em>Elixir</em> lorsque tu fais <strong>x = 1</strong>, le langage essaye de <em>matcher</em> l'expression de droite avec celle de gauche.
On pourra voir l'assignation de la variable x comme une conséquence de ce <em>pattern matching</em>.</p>
<p>Ça permet de faire tout un tas de choses, par exemple :</p>
<div class="highlight"><pre><span></span><span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="n">a</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="p">,</span><span class="w"> </span><span class="n">c</span><span class="p">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="ss">:hello</span><span class="p">,</span><span class="w"> </span><span class="s2">"world"</span><span class="p">,</span><span class="w"> </span><span class="mi">42</span><span class="p">}</span><span class="w"></span>
<span class="p">{</span><span class="ss">:hello</span><span class="p">,</span><span class="w"> </span><span class="s2">"world"</span><span class="p">,</span><span class="w"> </span><span class="mi">42</span><span class="p">}</span><span class="w"></span>
<span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="n">a</span><span class="w"></span>
<span class="ss">:hello</span><span class="w"></span>
<span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="n">b</span><span class="w"></span>
<span class="s2">"world"</span><span class="w"></span>
</pre></div>
<p>Tu remarqueras le <strong>:hello</strong>, qui est en réalité un <em>atom</em>. C'est en gros une constante qui porte comme nom de variable sa valeur. les <strong>true</strong> et <strong>false</strong> du langage
sont des <em>atoms</em> par exemple.</p>
<p>Pour récupérer le <em>head</em> et le <em>tail</em> d'une liste via le <em>pattern matching</em> :</p>
<div class="highlight"><pre><span></span><span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="p">[</span><span class="n">head</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">tail</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">]</span><span class="w"></span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">]</span><span class="w"></span>
<span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="n">head</span><span class="w"></span>
<span class="mi">1</span><span class="w"></span>
<span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="n">tail</span><span class="w"></span>
<span class="p">[</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">]</span><span class="w"></span>
</pre></div>
<p>Mais là où c'est vraiment fort, c'est que ce <em>pattern matching</em> fonctionne également pour les arguments des fonctions.
Et on va alors pouvoir écrire une fonction qui va se comporter différemment en fonction des arguments d'entrée.
Ça peut faire penser à <em>Java</em>, mais via le <em>pattern matching</em>, c'est vraiment plus puissant.</p>
<p>On imagine ici une fonction que se comporterait différemment en fonction du <em>JSON</em> qu'elle a reçu par exemple.
Ici, on effectue des répartions différentes selon le véhicule passé en entrée.</p>
<div class="highlight"><pre><span></span><span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="kd">defmodule</span><span class="w"> </span><span class="nc">Garage</span><span class="w"> </span><span class="k">do</span><span class="w"></span>
<span class="w"> </span><span class="kd">def</span><span class="w"> </span><span class="n">repair</span><span class="p">(%{</span><span class="s2">"voitures"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="p">%{</span><span class="w"> </span><span class="s2">"marque"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="s2">"peugeot"</span><span class="p">}})</span><span class="w"> </span><span class="k">do</span><span class="w"></span>
<span class="w"> </span><span class="nc">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="s2">"on traite les voitures peugeots"</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="k">end</span><span class="w"></span>
<span class="w"> </span><span class="kd">def</span><span class="w"> </span><span class="n">repair</span><span class="p">(%{</span><span class="s2">"motos"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="p">%{</span><span class="w"> </span><span class="s2">"couleur"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="s2">"rouge"</span><span class="p">}})</span><span class="w"> </span><span class="k">do</span><span class="w"></span>
<span class="w"> </span><span class="nc">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="s2">"on traite les motos rouges"</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="k">end</span><span class="w"></span>
<span class="w"> </span><span class="kd">def</span><span class="w"> </span><span class="n">repair</span><span class="p">(</span><span class="bp">_</span><span class="p">)</span><span class="w"> </span><span class="k">do</span><span class="w"></span>
<span class="w"> </span><span class="nc">IO</span><span class="o">.</span><span class="n">puts</span><span class="p">(</span><span class="s2">"on traite le reste"</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="k">end</span><span class="w"></span>
<span class="w"> </span><span class="k">end</span><span class="w"></span>
<span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="nc">Garage</span><span class="o">.</span><span class="n">repair</span><span class="p">(%{</span><span class="w"></span>
<span class="w"> </span><span class="s2">"voitures"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="p">%{</span><span class="w"></span>
<span class="w"> </span><span class="s2">"marque"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="s2">"peugeot"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"roues"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"couleur"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="s2">"rouge"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">})</span><span class="w"></span>
<span class="n">on</span><span class="w"> </span><span class="n">traite</span><span class="w"> </span><span class="n">les</span><span class="w"> </span><span class="n">voitures</span><span class="w"> </span><span class="n">peugeots</span><span class="w"></span>
<span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="nc">Garage</span><span class="o">.</span><span class="n">repair</span><span class="p">(%{</span><span class="w"></span>
<span class="w"> </span><span class="s2">"motos"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="p">%{</span><span class="w"></span>
<span class="w"> </span><span class="s2">"marque"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="s2">"ducati"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"roues"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"couleur"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="s2">"rouge"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">})</span><span class="w"></span>
<span class="n">on</span><span class="w"> </span><span class="n">traite</span><span class="w"> </span><span class="n">les</span><span class="w"> </span><span class="n">motos</span><span class="w"> </span><span class="n">rouges</span><span class="w"></span>
<span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="nc">Garage</span><span class="o">.</span><span class="n">repair</span><span class="p">(%{</span><span class="w"></span>
<span class="w"> </span><span class="s2">"motos"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="p">%{</span><span class="w"></span>
<span class="w"> </span><span class="s2">"marque"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="s2">"derbi"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"roues"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="s2">"couleur"</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="s2">"noir"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">})</span><span class="w"></span>
<span class="n">on</span><span class="w"> </span><span class="n">traite</span><span class="w"> </span><span class="n">le</span><span class="w"> </span><span class="n">reste</span><span class="w"></span>
</pre></div>
<p>Plutôt sympa nan ? Ça peut nous éviter pas mal de bloc type <em>if ... else</em> ce genre de syntaxe !</p>
</div>
<div class="section" id="pipe">
<h2>Pipe</h2>
<p>L'opérateur unix ultime, qui manque cruellement à <em>Python</em>, est bien présent dans Elixir !</p>
<p>Tu peux du coup faire ce genre de truc :</p>
<div class="highlight"><pre><span></span><span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="s2">"i love elixir"</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="nc">String</span><span class="o">.</span><span class="n">split</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="nc">Enum</span><span class="o">.</span><span class="n">take</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="nc">Enum</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">" "</span><span class="p">)</span><span class="w"> </span><span class="o">|></span><span class="w"> </span><span class="nc">String</span><span class="o">.</span><span class="n">capitalize</span><span class="w"></span>
<span class="s2">"Elixir"</span><span class="w"></span>
</pre></div>
</div>
<div class="section" id="processus">
<h2>Processus</h2>
<p>On en vient à ce qui permet à Elixir d'avoir des performances de malade. Elixir gère la concurrence à travers des processus internes.
Alors attention, ça n'a rien à voir avec les processus systèmes. C'est un fonctionnement interne à la VM <em>Erlang</em>, on peut donc en avoir des millions
sans que ça pose problème au niveau de la machine.</p>
<p>Pour ce faire, on a juste 3 mots clés à retenir : <strong>spawn</strong>, <strong>send</strong> et <strong>receive</strong>.</p>
<p>Avec <strong>spawn</strong>, on lance un nouveau processus, tandis que <strong>send</strong> et <strong>receive</strong> vont servir à envoyer et reçevoir des
messages à travers les processus.</p>
<p>Ça ressemble un peu au système de <em>goroutines</em> de <em>Go</em>. On pourrait voir le mot clé <strong>spawn</strong> équivalent au mot clé <strong>go</strong> de <em>Go</em>,
et le système <strong>send</strong>/<strong>receive</strong> comme le système de channel de <em>Go</em>.</p>
<div class="highlight"><pre><span></span><span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="n">parent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">self</span><span class="p">()</span><span class="w"></span>
<span class="c1">#PID<0.41.0></span><span class="w"></span>
<span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="n">spawn</span><span class="w"> </span><span class="k">fn</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">send</span><span class="p">(</span><span class="n">parent</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="ss">:hello</span><span class="p">,</span><span class="w"> </span><span class="n">self</span><span class="p">()})</span><span class="w"> </span><span class="k">end</span><span class="w"></span>
<span class="c1">#PID<0.48.0></span><span class="w"></span>
<span class="n">iex</span><span class="o">></span><span class="w"> </span><span class="k">receive</span><span class="w"> </span><span class="k">do</span><span class="w"></span>
<span class="n">...</span><span class="o">></span><span class="w"> </span><span class="p">{</span><span class="ss">:hello</span><span class="p">,</span><span class="w"> </span><span class="n">pid</span><span class="p">}</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="s2">"Got hello from </span><span class="si">#{</span><span class="n">inspect</span><span class="w"> </span><span class="n">pid</span><span class="si">}</span><span class="s2">"</span><span class="w"></span>
<span class="n">...</span><span class="o">></span><span class="w"> </span><span class="k">end</span><span class="w"></span>
<span class="s2">"Got hello from </span><span class="err">#</span><span class="s2">PID<0.48.0>"</span><span class="w"></span>
</pre></div>
<p>Dans cet exemple, on a notre processus parent qui reçoit un message du processus 48 qui a été <em>spawné</em>.</p>
</div>
<div class="section" id="bonus">
<h2>Bonus</h2>
<p>En bonus, on a également droit à tout ce qui vient du monde des langages fonctionnels, à savoir l'immutabilité, les <em>closures</em>,
les fonctions d'ordre supérieur, la récursivité à la place des boucles <em>for</em>, les fonctions pures etc ...</p>
<p>Voilà, j'espère t'avoir donné envie de t'intéresser à cet excellent langage ! Et ce n'est qu'un bref aperçu de toutes ses possibilités !</p>
</div>
Munch, un dictionnaire qui supporte les accesseurs2017-11-09T00:00:00+01:002017-11-09T00:00:00+01:00Morgantag:dotmobo.github.io,2017-11-09:/munch.html<p class="first last">Munch, un dictionnaire qui supporte les accesseurs</p>
<img alt="Python" class="align-right" src="./images/python.png" />
<p><a class="reference external" href="https://github.com/Infinidat/munch">Munch</a> est une petite librairie bien pratique et bien pensé.</p>
<p>En gros, elle permet d'utiliser les accesseurs pour accéder aux éléments de tes dictionnaires, à la manière d'un objet python.</p>
<p>Ça peut être utile notamment dans les tests unitaires, si tu as besoin de mocker rapidement un objet par exemple.</p>
<p>Tu écris un dictionnaire qui correspond à ton objet, tu le <em>munchify</em> et hop !</p>
<p>Si dans une fonction que tu veux tester, tu as utilisé des accesseurs sur un objet, ton <em>munch</em> sera considéré comme l'objet en question.</p>
<p>C'est à priori un fork de <a class="reference external" href="https://github.com/dsc/bunch">Bunch</a>, mais maintenu et qui fonctionne en python 3.</p>
<p>Tu l'installes avec <strong>pip</strong> :</p>
<div class="highlight"><pre><span></span>pip install munch
</pre></div>
<p>Tu importes <strong>munchify</strong> et tu convertis ton dictionnaire en <strong>munch</strong> :</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">munch</span> <span class="kn">import</span> <span class="n">munchify</span>
<span class="o">>>></span> <span class="n">data</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"last_name"</span><span class="p">:</span> <span class="s2">"Dupont"</span><span class="p">,</span>
<span class="s2">"first_name"</span><span class="p">:</span> <span class="s2">"Jean"</span><span class="p">,</span>
<span class="s2">"age"</span><span class="p">:</span> <span class="mi">30</span><span class="p">,</span>
<span class="s2">"activities"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"football"</span><span class="p">,</span> <span class="s2">"ping-pong"</span><span class="p">],</span>
<span class="s2">"job"</span><span class="p">:</span> <span class="p">{</span>
<span class="s2">"name"</span><span class="p">:</span> <span class="s2">"Developer"</span><span class="p">,</span>
<span class="s2">"enterprise"</span><span class="p">:</span> <span class="s2">"Mozilla"</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="o">>>></span> <span class="n">mymunch</span> <span class="o">=</span> <span class="n">munchify</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">mymunch</span><span class="o">.</span><span class="n">last_name</span>
<span class="s1">'Dupont'</span>
<span class="o">>>></span> <span class="n">mymunch</span><span class="o">.</span><span class="n">first_name</span> <span class="o">=</span> <span class="s2">"Pierre"</span>
<span class="o">>>></span> <span class="n">mymunch</span><span class="o">.</span><span class="n">first_name</span>
<span class="s1">'Pierre'</span>
<span class="o">>>></span> <span class="n">mymunch</span><span class="o">.</span><span class="n">activities</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="s1">'football'</span>
<span class="o">>>></span> <span class="n">mymunch</span><span class="o">.</span><span class="n">job</span><span class="o">.</span><span class="n">enterprise</span>
<span class="s1">'Mozilla'</span>
</pre></div>
<p>Voilà c'est tout !</p>
<p>Tu as accès aux différentes méthodes des dictionnaires, comme <em>keys()</em> et <em>update()</em>, ainsi qu'à l'itération et à l'opérateur <em>splat</em>.
Ça supporte également la sérialization en JSON et en YAML.</p>
Thématique DevOps avec Ansible2017-09-29T00:00:00+02:002017-09-29T00:00:00+02:00Morgantag:dotmobo.github.io,2017-09-29:/ansible.html<p class="first last">Thématique DevOps avec Ansible</p>
<img alt="Ansible" class="align-right" src="./images/ansible.png" />
<p><a class="reference external" href="https://www.ansible.com/">Ansible</a> est un <em>configuration management tool</em>, c'est-à-dire que c'est un outil qui va te permettre
d'administrer tes serveurs et d'y déployer automatiquement tes applications.</p>
<p><strong>Et franchement, ça bute !</strong></p>
<p>C'est très puissant tout en étant relativement simple à prendre en main, à l'inverse d'outils plus compliqués comme <em>Chef</em> ou <em>Puppet</em> par exemple.</p>
<p>En gros, ça va remplacer tes bons vieux scripts shell ou <a class="reference external" href="http://www.fabfile.org/">fabfile</a>.</p>
<div class="section" id="installation">
<h2>Installation</h2>
<div class="section" id="vagrant">
<h3>Vagrant</h3>
<p>Pour tester Ansible, tu vas utiliser <a class="reference external" href="https://www.virtualbox.org/">Virtualbox</a> avec <a class="reference external" href="https://www.vagrantup.com/downloads.html">Vagrant</a>
pour te monter deux petites VMs Ubuntu Xenial.</p>
<p>Sous Ubuntu, tu peux installer tout ça via la commande suivante et redémarrer ta machine si besoin:</p>
<div class="highlight"><pre><span></span>sudo apt-get install virtualbox virtualbox-dkms vagrant
</pre></div>
<p>Dans l'idéal, essaye d'avoir une version assez récente de Vagrant. Les fichiers de conf peuvent changer selon les versions.</p>
<p>Ensuite, tu te crées le fichier <em>Vagrantfile</em> suivant dans <em>~/tuto_ansible/vagrant/Vagrantfile</em> par exemple:</p>
<div class="highlight"><pre><span></span><span class="c1"># -*- mode: ruby -*-</span>
<span class="c1"># vi: set ft=ruby :</span>
<span class="no">Vagrant</span><span class="o">.</span><span class="n">configure</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
<span class="n">config</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">define</span> <span class="s2">"vm1"</span> <span class="k">do</span> <span class="o">|</span> <span class="n">vm1</span> <span class="o">|</span>
<span class="n">vm1</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">box</span> <span class="o">=</span> <span class="s2">"ubuntu/xenial64"</span>
<span class="n">config</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">network</span> <span class="ss">:forwarded_port</span><span class="p">,</span> <span class="ss">guest</span><span class="p">:</span> <span class="mi">22</span><span class="p">,</span> <span class="ss">host</span><span class="p">:</span> <span class="mi">2200</span><span class="p">,</span> <span class="nb">id</span><span class="p">:</span> <span class="s2">"ssh"</span>
<span class="n">config</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">network</span> <span class="ss">:forwarded_port</span><span class="p">,</span> <span class="ss">guest</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="ss">host</span><span class="p">:</span> <span class="mi">8010</span><span class="p">,</span> <span class="nb">id</span><span class="p">:</span> <span class="s2">"http"</span>
<span class="k">end</span>
<span class="n">config</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">define</span> <span class="s2">"vm2"</span> <span class="k">do</span> <span class="o">|</span> <span class="n">vm2</span> <span class="o">|</span>
<span class="n">vm2</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">box</span> <span class="o">=</span> <span class="s2">"ubuntu/xenial64"</span>
<span class="n">config</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">network</span> <span class="ss">:forwarded_port</span><span class="p">,</span> <span class="ss">guest</span><span class="p">:</span> <span class="mi">22</span><span class="p">,</span> <span class="ss">host</span><span class="p">:</span> <span class="mi">2201</span><span class="p">,</span> <span class="nb">id</span><span class="p">:</span> <span class="s2">"ssh"</span>
<span class="n">config</span><span class="o">.</span><span class="n">vm</span><span class="o">.</span><span class="n">network</span> <span class="ss">:forwarded_port</span><span class="p">,</span> <span class="ss">guest</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span> <span class="ss">host</span><span class="p">:</span> <span class="mi">8011</span><span class="p">,</span> <span class="nb">id</span><span class="p">:</span> <span class="s2">"http"</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Et tu démarres tes deux VMs via:</p>
<div class="highlight"><pre><span></span><span class="nb">cd</span> ~/tuto_ansible/vagrant/
vagrant up
</pre></div>
</div>
<div class="section" id="ansible-1">
<h3>Ansible</h3>
<p>Tu installes Ansible sous Ubuntu via :</p>
<div class="highlight"><pre><span></span>sudo apt-get install software-properties-common
sudo apt-add-repository ppa:ansible/ansible
sudo apt-get update
sudo apt-get install ansible
</pre></div>
<p>Puis tu crées un fichier <em>hosts</em> à la racine du projet, dans <em>~/tuto_ansible/hosts</em>:</p>
<div class="highlight"><pre><span></span><span class="o">[</span>ubuntu<span class="o">]</span>
vm1 <span class="nv">ansible_host</span><span class="o">=</span><span class="m">127</span>.0.0.1 <span class="nv">ansible_port</span><span class="o">=</span><span class="m">2200</span> <span class="nv">ansible_user</span><span class="o">=</span>ubuntu <span class="nv">ansible_become</span><span class="o">=</span>yes <span class="nv">env</span><span class="o">=</span><span class="nb">test</span>
vm2 <span class="nv">ansible_host</span><span class="o">=</span><span class="m">127</span>.0.0.1 <span class="nv">ansible_port</span><span class="o">=</span><span class="m">2201</span> <span class="nv">ansible_user</span><span class="o">=</span>ubuntu <span class="nv">ansible_become</span><span class="o">=</span>yes <span class="nv">env</span><span class="o">=</span>prod
</pre></div>
<p>Et tu édites un fichier <em>ansible.cfg</em> qui va contenir ta configuration Ansible, dans <em>~/tuto_ansible/ansible.cfg</em>:</p>
<div class="highlight"><pre><span></span><span class="o">[</span>defaults<span class="o">]</span>
<span class="nv">inventory</span> <span class="o">=</span> hosts
</pre></div>
<p>Tu as désormais indiqué à Ansible d'utiliser tes deux Vms précédemment créées.</p>
<p>Tout est prêt !</p>
</div>
</div>
<div class="section" id="la-commande-ansible">
<h2>La commande ansible</h2>
<p>Déjà, tu te places dans le répertoire <em>~/tuto_ansible</em> pour pouvoir lancer les commandes:</p>
<div class="highlight"><pre><span></span><span class="nb">cd</span> ~/tuto_ansible
</pre></div>
<p>Ansible a besoin de python sur les machines clientes, donc si c'est pas installé par défaut:</p>
<div class="highlight"><pre><span></span>ansible all -m raw -a <span class="s2">"apt install -y python"</span> --ask-sudo-pass
</pre></div>
<p>L'astuce ici c'est que l'option <strong>-m raw</strong> permet d'exécuter directement une commande ssh sans Ansible.</p>
<p>Et tu essayes maintenant de contacter tes deux VMs via:</p>
<div class="highlight"><pre><span></span>ansible all -m ping
</pre></div>
<p>Si tout est ok à ce niveau-là, tu peux passer à la suite. Sinon c'est qu'il y a un souci quelque-part.</p>
<p>Tu vas maintenant pouvoir utiliser la commande <strong>ansible</strong> pour faire des trucs sur les serveurs comme:</p>
<ul class="simple">
<li>Exécuter une commande pour afficher la liste des utilisateurs de la première machine:</li>
</ul>
<div class="highlight"><pre><span></span>ansible vm1 -m shell -a <span class="s2">"cat /etc/passwd"</span>
</pre></div>
<ul class="simple">
<li>S’assurer que <em>openssl</em> et <em>bash</em> sont à jour sur tous les serveurs ubuntu.</li>
</ul>
<div class="highlight"><pre><span></span>ansible ubuntu -m apt -a <span class="s2">"name=openssl,bash state=latest"</span>
</pre></div>
<ul class="simple">
<li>Créer un utilisateur <em>ansible</em> avec un shell <em>/bin/bash</em>.</li>
</ul>
<div class="highlight"><pre><span></span>ansible ubuntu -m user -a <span class="s2">"name=ansible shell=/bin/bash"</span>
</pre></div>
<ul class="simple">
<li>Installer la clef publique SSH de notre utilisateur sur l’utilisateur <em>ansible</em>.</li>
</ul>
<div class="highlight"><pre><span></span>ansible ubuntu -m authorized_key -a <span class="s2">"user=ansible state=present key={{ lookup('file', '~/.ssh/id_rsa.pub') }}"</span>
</pre></div>
<ul class="simple">
<li>S’assurer des bons droits sur <em>/etc/passwd (0644)</em> et <em>/etc/shadow (0400)</em>.</li>
</ul>
<div class="highlight"><pre><span></span>ansible ubuntu -m file -a <span class="s2">"path=/etc/passwd mode=0664"</span>
ansible ubuntu -m file -a <span class="s2">"path=/etc/shadow mode=0400"</span>
</pre></div>
</div>
<div class="section" id="playbooks">
<h2>Playbooks</h2>
<p>La commande <strong>ansible</strong> c'est bien mais ça va un moment. Ce que tu veux, c'est avoir une recette à exécuter qui s'occupe de mettre
à jour ou non ce dont tu as besoin sur les machines distantes. Cette recette, ça s'appelle un <strong>playbook</strong>.</p>
<p>Tu vas donc écrire un <strong>playbook</strong> permettant de :</p>
<ul class="simple">
<li>installer le paquet sudo.</li>
<li>créer un utilisateur ansible.</li>
<li>importer une clé SSH publique pour cet utilisateur.</li>
<li>configurer sudo pour cet utilisateur (sans mot de passe).</li>
<li>installer Apache avec le support de PHP activé pour les distributions Ubuntu.</li>
</ul>
<p>Tu crées le fichier <em>~/tuto_ansible/myplaybook1.yml</em> :</p>
<div class="highlight"><pre><span></span><span class="nn">---</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">hosts</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ubuntu</span><span class="w"></span>
<span class="w"> </span><span class="nt">become</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">yes</span><span class="w"></span>
<span class="w"> </span><span class="nt">tasks</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">"Installation</span><span class="nv"> </span><span class="s">de</span><span class="nv"> </span><span class="s">sudo</span><span class="nv"> </span><span class="s">sur</span><span class="nv"> </span><span class="s">{{</span><span class="nv"> </span><span class="s">ansible_distribution</span><span class="nv"> </span><span class="s">}}"</span><span class="w"></span>
<span class="w"> </span><span class="nt">apt</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">sudo</span><span class="w"></span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">latest</span><span class="w"></span>
<span class="w"> </span><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ansible_distribution == 'Ubuntu'</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Ajout d'un user</span><span class="w"></span>
<span class="w"> </span><span class="nt">user</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ansible</span><span class="w"></span>
<span class="w"> </span><span class="nt">shell</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/bin/bash</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Clé ssh</span><span class="w"></span>
<span class="w"> </span><span class="nt">authorized_key</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">user</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ansible</span><span class="w"></span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">present</span><span class="w"></span>
<span class="w"> </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="s">"{{</span><span class="nv"> </span><span class="s">lookup('file',</span><span class="nv"> </span><span class="s">'~/.ssh/id_rsa.pub')</span><span class="nv"> </span><span class="s">}}"</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Ajout du user dans les sudoers</span><span class="w"></span>
<span class="w"> </span><span class="nt">lineinfile</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">dest</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/etc/sudoers</span><span class="w"></span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">present</span><span class="w"></span>
<span class="w"> </span><span class="nt">line</span><span class="p">:</span><span class="w"> </span><span class="s">'ansible</span><span class="nv"> </span><span class="s">ALL=(ALL)</span><span class="nv"> </span><span class="s">NOPASSWD:</span><span class="nv"> </span><span class="s">ALL'</span><span class="w"></span>
<span class="w"> </span><span class="nt">validate</span><span class="p">:</span><span class="w"> </span><span class="s">'visudo</span><span class="nv"> </span><span class="s">-cf</span><span class="nv"> </span><span class="s">%s'</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="s">"Installation</span><span class="nv"> </span><span class="s">de</span><span class="nv"> </span><span class="s">apache</span><span class="nv"> </span><span class="s">sur</span><span class="nv"> </span><span class="s">{{</span><span class="nv"> </span><span class="s">ansible_distribution</span><span class="nv"> </span><span class="s">}}"</span><span class="w"></span>
<span class="w"> </span><span class="nt">apt</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">apache2</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">php</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">libapache2-mod-php</span><span class="w"></span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">latest</span><span class="w"></span>
<span class="w"> </span><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ansible_distribution == 'Ubuntu'</span><span class="w"></span>
<span class="w"> </span><span class="nt">tags</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">install_apache</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Apache php</span><span class="w"></span>
<span class="w"> </span><span class="nt">apache2_module</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">php7.0</span><span class="w"></span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">present</span><span class="w"></span>
<span class="w"> </span><span class="nt">notify</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Restart Apache</span><span class="w"></span>
<span class="w"> </span><span class="nt">handlers</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Restart Apache</span><span class="w"></span>
<span class="w"> </span><span class="nt">service</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">apache2</span><span class="w"></span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">restarted</span><span class="w"></span>
</pre></div>
<p>Regarde bien en détail le playbook:</p>
<ul class="simple">
<li><em>hosts</em> précise sur quelles machines exécuter les tâches.</li>
<li><em>become</em> indique qu'il faut être <em>sudoer</em>.</li>
<li><em>tasks</em> contient les différentes tâches à lancer.</li>
<li><em>apt</em>, <em>user</em>, <em>authorized_key</em>, <em>lineinfile</em>, <em>apache2_module</em> et <em>service</em> sont <a class="reference external" href="http://docs.ansible.com/ansible/latest/list_of_all_modules.html">des modules</a>.</li>
<li><em>when</em> permet d'utiliser de la conditionnalité pour l'exécution des tâches.</li>
<li><em>name</em>, <em>state</em>, <em>key</em>, <em>shell</em>, <em>dest</em>, <em>line</em> et autres sont les paramètres des modules. Pour voir la doc d'un module, tu peux utiliser la commande:</li>
</ul>
<div class="highlight"><pre><span></span>ansible-doc apache2_module
</pre></div>
<p>Tu exécutes ton <em>playbook</em>:</p>
<div class="highlight"><pre><span></span>ansible-playbook myplaybook1.yml
</pre></div>
<p>Et si tu te rends sur <em>http://127.0.0.1:8010/</em> et <em>http://127.0.0.1:8011/</em>, tu obtiens bien la page
par défaut de Apache:</p>
<div class="highlight"><pre><span></span>wget http://127.0.0.1:8010/
wget http://127.0.0.1:8011/
</pre></div>
<p>Tu peux relancer plusieurs fois de suite le playbook, Ansible ne fera rien de plus sur les serveurs car rien n'a changé.</p>
</div>
<div class="section" id="templates">
<h2>Templates</h2>
<p>Dans ton playbook, tu peux également utiliser des templates pour déployer des fichiers de configuration.
Si tu as l'habitude de Django ou Flask, ça tombe bien car c'est <a class="reference external" href="http://jinja.pocoo.org/">Jinja</a> qui est utilisé par Ansible !</p>
<p>Tu vas maintenant mettre en place un <em>message du jour</em> (motd) sur les serveurs à l'aide d'un template.</p>
<p>Tu crées le template <strong>motd</strong> suivant dans <em>~/tuto_ansible/motd</em>:</p>
<div class="highlight"><pre><span></span><span class="nv">IP</span> <span class="o">=</span> <span class="o">{{</span> ansible_default_ipv4.address <span class="o">}}</span>
<span class="nv">OS</span> <span class="o">=</span> <span class="o">{{</span> ansible_distribution <span class="o">}}</span>
<span class="o">{</span>% <span class="k">if</span> <span class="nv">env</span> <span class="o">==</span> <span class="s1">'prod'</span> %<span class="o">}</span>
<span class="nv">ENV</span> <span class="o">=</span> Production
<span class="o">{</span>% <span class="k">elif</span> <span class="nv">env</span> <span class="o">==</span> <span class="s1">'test'</span> %<span class="o">}</span>
<span class="nv">ENV</span> <span class="o">=</span> Test
<span class="o">{</span>% endif %<span class="o">}</span>
</pre></div>
<p>Et le playbook associé dans <em>~/tuto_ansible/motd.yml</em>:</p>
<div class="highlight"><pre><span></span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">hosts</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ubuntu</span><span class="w"></span>
<span class="w"> </span><span class="nt">become</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span><span class="w"></span>
<span class="w"> </span><span class="nt">tasks</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">template</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">src</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">motd</span><span class="w"></span>
<span class="w"> </span><span class="nt">dest</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/etc/motd</span><span class="w"></span>
<span class="w"> </span><span class="nt">owner</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ansible</span><span class="w"></span>
<span class="w"> </span><span class="nt">group</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ansible</span><span class="w"></span>
<span class="w"> </span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">0640</span><span class="w"></span>
<span class="w"> </span><span class="nt">backup</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">yes</span><span class="w"></span>
</pre></div>
<p>Tu lances ton playbook et tu testes tout ça:</p>
<div class="highlight"><pre><span></span>ansible-playbook motd.yml
</pre></div>
<p>Tu devrais voir:</p>
<div class="highlight"><pre><span></span>ssh -p <span class="m">2200</span> ubuntu@127.0.0.1
...
<span class="nv">IP</span> <span class="o">=</span> <span class="m">10</span>.0.2.15
<span class="nv">OS</span> <span class="o">=</span> Ubuntu
<span class="nv">ENV</span> <span class="o">=</span> Test
Last login: Fri Sep <span class="m">29</span> <span class="m">06</span>:45:30 <span class="m">2017</span> from <span class="m">10</span>.0.2.2
ubuntu@ubuntu-xenial:~$
</pre></div>
<p>Et:</p>
<div class="highlight"><pre><span></span>ssh -p <span class="m">2201</span> ubuntu@127.0.0.1
...
<span class="nv">IP</span> <span class="o">=</span> <span class="m">10</span>.0.2.15
<span class="nv">OS</span> <span class="o">=</span> Ubuntu
<span class="nv">ENV</span> <span class="o">=</span> Production
Last login: Fri Sep <span class="m">29</span> <span class="m">06</span>:45:30 <span class="m">2017</span> from <span class="m">10</span>.0.2.2
ubuntu@ubuntu-xenial:~$
</pre></div>
</div>
<div class="section" id="roles">
<h2>Rôles</h2>
<p>Ce qui serait bien, ça serait de pouvoir organiser un peu mieux tout ça pour que tes playbooks soient réutilisables.
Tu vas pour ce faire créer un rôle <strong>apache</strong> qui va installer Apache et le configurer à l'aide d'un template.</p>
<p>C'est parti ! Tu crées un répertoire <em>~/tuto_ansible/roles</em> qui contient l'arborescence suivante:</p>
<div class="highlight"><pre><span></span>roles
└── apache
├── handlers
│ └── main.yml
├── tasks
│ └── main.yml
└── templates
└── mysite.j2
</pre></div>
<p>Ou alors tu peux utiliser la commande <strong>ansible-galaxy</strong> pour initialiser l'arborescence complète d'un rôle:</p>
<div class="highlight"><pre><span></span>ansible-galaxy init --offline --init-path ~/tuto_ansible/roles apache
</pre></div>
<p>Le dossier <em>handlers</em> va te permettre d'y mettre des tâches qui vont être exécutées à chaque notification de changement.
C'est utile pour redémarrer Apache dès que c'est nécessaire par exemple.</p>
<p>Dans <em>~/tuto_ansible/roles/apache/handlers/main.yml</em>, tu mets:</p>
<div class="highlight"><pre><span></span><span class="nn">---</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Restart Apache</span><span class="w"></span>
<span class="w"> </span><span class="nt">service</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">apache2</span><span class="w"></span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">restarted</span><span class="w"></span>
</pre></div>
<p>Le dossiers <em>tasks</em> va tout simplement contenir tes tâches.</p>
<p>Dans <em>~/tuto_ansible/roles/apache/tasks/main.yml</em>, tu mets:</p>
<div class="highlight"><pre><span></span><span class="nn">---</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Install Apache</span><span class="w"></span>
<span class="w"> </span><span class="nt">apt</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">apache2</span><span class="w"></span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">present</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Check Apache</span><span class="w"></span>
<span class="w"> </span><span class="nt">service</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">apache2</span><span class="w"></span>
<span class="w"> </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span><span class="w"></span>
<span class="w"> </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">started</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Installation d'un vhost</span><span class="w"></span>
<span class="w"> </span><span class="nt">template</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">src</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">mysite.j2</span><span class="w"></span>
<span class="w"> </span><span class="nt">dest</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">/etc/apache2/sites-available/mysite.conf</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">a2ensite mysite</span><span class="w"></span>
<span class="w"> </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">a2ensite mysite</span><span class="w"></span>
<span class="w"> </span><span class="nt">notify</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Restart Apache</span><span class="w"></span>
</pre></div>
<p>Ça installe et démarre Apache, et ça déploie et active un site qui va utiliser la configuration du template <em>mysite.j2</em>.</p>
<p>Pour finir, dans le dossier <em>templates</em> sous <em>~/tuto_ansible/roles/apache/templates/mysite.j2</em>, tu vas mettre la configuration de ton Virtual Host Apache:</p>
<div class="highlight"><pre><span></span><VirtualHost *:<span class="o">{{</span> http_port <span class="o">}}</span>>
ServerAdmin webmaster@<span class="o">{{</span> domain <span class="o">}}</span>
ServerName <span class="o">{{</span> domain <span class="o">}}</span>
DocumentRoot /var/www/
ErrorLog <span class="si">${</span><span class="nv">APACHE_LOG_DIR</span><span class="si">}</span>/error.log
CustomLog <span class="si">${</span><span class="nv">APACHE_LOG_DIR</span><span class="si">}</span>/access.log combined
</VirtualHost>
</pre></div>
<p>Les variables <strong>http_port</strong> et <strong>domain</strong> seront à définir dans ton playbook principal.
Tu édites donc un fichier <em>~/tuto_ansible/playbook-apache.yml</em> et tu y configures ton rôle <strong>apache</strong>:</p>
<div class="highlight"><pre><span></span><span class="nn">---</span><span class="w"></span>
<span class="c1"># role apache</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="nt">hosts</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">ubuntu</span><span class="w"></span>
<span class="w"> </span><span class="nt">become</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">true</span><span class="w"></span>
<span class="w"> </span><span class="nt">vars</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="nt">http_port</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">80</span><span class="w"></span>
<span class="w"> </span><span class="nt">domain</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">localhost</span><span class="w"></span>
<span class="w"> </span><span class="nt">roles</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">apache</span><span class="w"></span>
</pre></div>
<p>Tu l'exécutes:</p>
<div class="highlight"><pre><span></span>ansible-playbook playbook-apache.yml
</pre></div>
<p>Et voilà Apache est bien installé et configuré !</p>
<p>Pour aller un peu plus loin, tu peux jeter un oeil aux rôles disponibles sur <a class="reference external" href="https://galaxy.ansible.com/">Galaxy</a>,
tu y trouveras sûrement ton bonheur !</p>
</div>
Découverte du gestionnaire de files de tâches Celery2017-09-28T00:00:00+02:002017-09-28T00:00:00+02:00Morgantag:dotmobo.github.io,2017-09-28:/celery.html<p class="first last">Découverte du gestionnaire de files de tâches Celery</p>
<img alt="Celery" class="align-right" src="./images/celery.png" />
<p><strong>C'est quoi ? Ça se mange ?</strong></p>
<p><a class="reference external" href="http://www.celeryproject.org/">Celery</a> est un gestionnaire de tâches <em>asynchrone</em>.
Il s'occupe de créer des files d'attentes pour les tâches, de les distribuer sur un ou des <em>workers</em> et
d'en répartir la charge. Intégré dans une application web, il va permettre d'ordonner l'exécution de tâches
en background.</p>
<p>Cet outil va t'être utile dans de nombreux cas d'usages. Personnellement, je l'utilise pour une application de streaming
de vidéos et il me permet de lancer les jobs d'encodage des médias sur différents serveurs.</p>
<p>Mais tu vas pouvoir lui laisser le soin de zipper/dézipper de gros fichiers, générer de nombreux documents pdf, lancer
le téléchargement de fichiers en masse ou encore lancer des calculs sur plusieurs machines.</p>
<p><strong>Mais pourquoi utiliser Celery et pas un autre outil ?</strong></p>
<p>Déjà, il est mature et très utilisé dans la communauté Python. Son côté <em>asynchrone</em> lui donne de bonnes performances.
De plus, il en existe une intégration dans Django. Il y a des clients implémentés dans d'autres languages, comme php ou node.
Grâce à lui, tu vas pouvoir automatiser tout un tas de trucs en répartissant les <em>workers</em> et les clients sur différentes machines.</p>
<p><strong>Broker, quésaco ?</strong></p>
<p>La première notion à connaître est le <strong>broker</strong>. Il s'agit tout simplement de la file d'attente.
Tu vas pouvoir utiliser plusieurs technos pour gérer le <em>broker</em>, comme RabbitMq, Redis, Mongodb, Sqlalchemy, ou même l'orm de Django.</p>
<p>C'est le <em>broker</em> qui va permettre la communication entre le(s) workers(s) et le(s) client(s). La techno recommandée par Celery est <a class="reference external" href="https://www.rabbitmq.com/">RabbitMq</a>,
qui utilise le protocole <strong>AMQP</strong>. Je t'en ai <a class="reference external" href="http://dotmobo.github.io/rabbitmq.html">déjà parlé précédemment</a>.</p>
<p><strong>Backend, quésaco ?</strong></p>
<p>La seconde notion à connaître est le <strong>backend</strong>. Celui-ci est optionnel et permet de monitorer et de garder une trace des états des tâches.
Les options de stockage sont quasi-similaires à celle du broker (RabbitMq, Redis, etc ...)</p>
<p><strong>Mon application web qui génère des archives AVANT Celery</strong></p>
<p>Prenons l'exemple d'une petite application web qui a pour objectif de générer un document au format pdf pour chaque usager présent dans une base de
données, par année de naissance.</p>
<ul class="simple">
<li>Étape 1 : choisir une année de naissance</li>
<li>Étape 2 : cliquer sur "Générer"</li>
<li>Étape 3 : regarder la roue qui tourne <img alt="waiting" src="./images/loading.gif" /></li>
<li>Étape 4 : se faire un café</li>
<li>Étape 5 : contempler la roue qui tourne</li>
<li>Étape 6 : se faire un autre café</li>
<li>Étape 7 : 504 Gateway Timeout</li>
</ul>
<p><strong>Mon application web qui génère des archives AVEC Celery</strong></p>
<ul class="simple">
<li>Étape 1 : choisir une année de naissance</li>
<li>Étape 2 : cliquer sur "Générer"</li>
<li>Étape 3 : choisir une autre année</li>
<li>Étape 4 : cliquer sur "Générer"</li>
<li>Étape 5 : choisir une autre année</li>
<li>Étape 6 : cliquer sur "Générer"</li>
<li>Étape 7: Mince, il est où mon café ?</li>
</ul>
<p><strong>Objectif</strong></p>
<p>Pour se faire, l'objectif est d'arriver à une architecture de ce type:</p>
<p><img alt="objectif" src="./images/django_celery_architecture.png" /></p>
<p><strong>Installation</strong></p>
<p>Tu commences par installer RabbitMq:</p>
<ul class="simple">
<li>Soit sous Ubuntu (tu prends la dernière version ici):</li>
</ul>
<div class="highlight"><pre><span></span><span class="nb">echo</span> <span class="s1">'deb http://www.rabbitmq.com/debian/ testing main'</span> <span class="p">|</span> sudo tee /etc/apt/sources.list.d/rabbitmq.list
wget -O- https://www.rabbitmq.com/rabbitmq-release-signing-key.asc <span class="p">|</span> sudo apt-key add -
sudo apt-get update <span class="o">&&</span> apt-get install rabbitmq-server
</pre></div>
<ul class="simple">
<li>Soit via Docker:</li>
</ul>
<div class="highlight"><pre><span></span>docker run -d --hostname myrabbitmq --name myrabbitmq -p <span class="m">5672</span>:5672 rabbitmq:3
</pre></div>
<p>Puis tu installes Celery avec pip:</p>
<div class="highlight"><pre><span></span>pip install <span class="nv">celery</span><span class="o">==</span><span class="m">3</span>.1.25
</pre></div>
<p>Oui je sais, il y a la version 4.1 de Celery qui est sortie cet été. Mais je n'ai pas encore eu le temps de me pencher dessus, donc il
faudra sûrement mettre à jour ce tuto à l'avenir ! Désolé mon vieux !</p>
<p><strong>Django - Configuration</strong></p>
<p>Dans ton fichier de <em>settings</em> de django, par exemple <em>myproject/settings.py</em>, tu ajoutes les paramètres suivants:</p>
<div class="highlight"><pre><span></span><span class="n">CELERY_NAME</span> <span class="o">=</span> <span class="s2">"myproject"</span>
<span class="n">CELERY_BACKEND</span> <span class="o">=</span> <span class="s2">"amqp"</span>
<span class="n">CELERY_BROKER</span> <span class="o">=</span> <span class="s2">"amqp://guest@localhost//"</span>
</pre></div>
<p><strong>Django - Worker</strong></p>
<p>Ensuite, tu crées ton worker <em>myproject/celery.py</em>:</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">from</span> <span class="nn">celery</span> <span class="kn">import</span> <span class="n">Celery</span>
<span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s2">"DJANGO_SETTINGS_MODULE"</span><span class="p">,</span> <span class="s2">"myproject.settings"</span><span class="p">)</span>
<span class="kn">from</span> <span class="nn">django.conf</span> <span class="kn">import</span> <span class="n">settings</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">Celery</span><span class="p">(</span><span class="n">settings</span><span class="o">.</span><span class="n">CELERY_NAME</span><span class="p">,</span> <span class="n">backend</span><span class="o">=</span><span class="n">settings</span><span class="o">.</span><span class="n">CELERY_BACKEND</span>
<span class="n">broker</span><span class="o">=</span><span class="n">settings</span><span class="o">.</span><span class="n">CELERY_BROKER</span><span class="p">)</span>
<span class="n">app</span><span class="o">.</span><span class="n">config_from_object</span><span class="p">(</span><span class="s1">'django.conf:settings'</span><span class="p">)</span>
<span class="n">app</span><span class="o">.</span><span class="n">autodiscover_tasks</span><span class="p">(</span><span class="k">lambda</span><span class="p">:</span> <span class="n">settings</span><span class="o">.</span><span class="n">INSTALLED_APPS</span><span class="p">)</span>
<span class="nd">@app</span><span class="o">.</span><span class="n">task</span><span class="p">(</span><span class="n">bind</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">debug_task</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'Request: </span><span class="si">{0!r}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">request</span><span class="p">))</span>
</pre></div>
<p><strong>Django - Création des tâches</strong></p>
<p>Puis, tu crées les tâches qui vont te permettre de générer les documents pdf dans <em>myproject/apps/myapp/tasks.py</em>:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">celery</span> <span class="kn">import</span> <span class="n">shared_task</span>
<span class="kn">from</span> <span class="nn">celery.signals</span> <span class="kn">import</span> <span class="n">task_prerun</span><span class="p">,</span> <span class="n">task_success</span><span class="p">,</span> <span class="n">task_failure</span>
<span class="kn">from</span> <span class="nn">.utils</span> <span class="kn">import</span> <span class="n">get_persons_and_generate_pdfs</span>
<span class="nd">@shared_task</span><span class="p">(</span><span class="n">bind</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> <span class="c1"># utile pour les "reusable apps"</span>
<span class="k">def</span> <span class="nf">task_generate_archive_files</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">date</span><span class="p">):</span>
<span class="sd">""" generate pdf files """</span>
<span class="n">get_persons_and_generate_pdfs</span><span class="p">(</span><span class="n">date</span><span class="p">)</span>
<span class="nd">@task_prerun</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">sender</span><span class="o">=</span><span class="n">task_generate_archive_files</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">start_generate_archive_files</span><span class="p">(</span><span class="n">sender</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Initialisation du statut du lot en base"</span><span class="p">)</span>
<span class="nd">@task_success</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">sender</span><span class="o">=</span><span class="n">task_generate_archive_files</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">success_generate_archive_files</span><span class="p">(</span><span class="n">sender</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Modification du statut du lot en réussi"</span><span class="p">)</span>
<span class="nd">@task_failure</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">sender</span><span class="o">=</span><span class="n">task_generate_archive_files</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">failure_generate_archive_files</span><span class="p">(</span><span class="n">sender</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Modification du statut du lot en échec"</span><span class="p">)</span>
</pre></div>
<p><strong>Django - Appel des tâches</strong></p>
<p>Enfin, tu gères l'appelles des tâches dans <em>myproject/apps/myapp/views.py</em>, par exemple:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">.tasks</span> <span class="kn">import</span> <span class="n">task_generate_archive_files</span>
<span class="nd">@login_required</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">generate_archive_files</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span> <span class="o">==</span> <span class="s2">"POST"</span><span class="p">:</span>
<span class="n">date</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">POST</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'birth_date'</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">my_task</span> <span class="o">=</span> <span class="n">task_generate_archive_files</span><span class="o">.</span><span class="n">delay</span><span class="p">(</span><span class="n">date</span><span class="p">)</span>
<span class="k">except</span> <span class="n">IntegrityError</span><span class="p">:</span>
<span class="n">messages</span><span class="o">.</span><span class="n">add_message</span><span class="p">(</span>
<span class="n">request</span><span class="p">,</span> <span class="n">messages</span><span class="o">.</span><span class="n">ERROR</span><span class="p">,</span> <span class="n">_</span><span class="p">(</span><span class="s2">"A file for one of those persons already exists"</span><span class="p">))</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">messages</span><span class="o">.</span><span class="n">add_message</span><span class="p">(</span>
<span class="n">request</span><span class="p">,</span> <span class="n">messages</span><span class="o">.</span><span class="n">INFO</span><span class="p">,</span> <span class="n">_</span><span class="p">(</span><span class="s2">"A file creation task is scheduled"</span><span class="p">))</span>
<span class="k">return</span> <span class="n">redirect</span><span class="p">(</span><span class="n">reverse</span><span class="p">(</span><span class="s2">"myproject-database:database"</span><span class="p">,</span> <span class="s2">"?birth_date=</span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">date</span><span class="p">)))</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="n">HttpResponseForbidden</span><span class="p">()</span>
</pre></div>
<p>À toi d'adapter le code pour que ça colle avec ton projet, tes urls, et autres. C'est qu'un exemple d'utilisation.</p>
<p><strong>Django - Exécution</strong></p>
<p>Pour exécuter celery, tu peux alors lancer la commande suivante:</p>
<div class="highlight"><pre><span></span>celery -A myproject worker -l info
</pre></div>
<p>Tu peux même te faire un petit <strong>Makefile</strong> dans ce genre:</p>
<div class="highlight"><pre><span></span>RABBITMQ :<span class="o">=</span> rabbitmq-myproject
CELERY :<span class="o">=</span> myproject
run-rabbitmq:
docker ps -aq --filter <span class="nv">name</span><span class="o">=</span><span class="k">$(</span>RABBITMQ<span class="k">)</span> <span class="p">|</span> xargs -r docker rm -f -v <span class="o">&&</span> <span class="se">\</span>
docker run -d --hostname <span class="k">$(</span>RABBITMQ<span class="k">)</span> --name <span class="k">$(</span>RABBITMQ<span class="k">)</span> -p <span class="m">5672</span>:5672 rabbitmq:3
run-celery: run-rabbitmq
celery -A <span class="k">$(</span>CELERY<span class="k">)</span> worker -l info
.PHONY: un-rabbitmq run-celery
</pre></div>
<p><strong>Debug</strong></p>
<p>Tu vas alors avoir un écran de debug qui ressemble à ça:</p>
<div class="highlight"><pre><span></span><span class="o">[</span><span class="m">2016</span>-10-21 <span class="m">16</span>:47:18,568: INFO/MainProcess<span class="o">]</span> Connected to amqp://guest:**@127.0.0.1:5672//
<span class="o">[</span><span class="m">2016</span>-10-21 <span class="m">16</span>:47:18,615: INFO/MainProcess<span class="o">]</span> mingle: searching <span class="k">for</span> neighbors
<span class="o">[</span><span class="m">2016</span>-10-21 <span class="m">16</span>:47:19,628: INFO/MainProcess<span class="o">]</span> mingle: all alone
<span class="o">[</span><span class="m">2016</span>-10-21 <span class="m">16</span>:50:23,354: INFO/MainProcess<span class="o">]</span> Received task: myproject.apps.file.
tasks.task_generate_archive_files<span class="o">[</span>1755cd30-03f5-4d8a-8d92-fa5b1853a209<span class="o">]</span>
...
...
...
<span class="o">[</span><span class="m">2016</span>-10-21 <span class="m">16</span>:50:26,944: INFO/MainProcess<span class="o">]</span> Task myproject.apps.file.
tasks.task_generate_archive_files<span class="o">[</span>1755cd30-03f5-4d8a-8d92-fa5b1853a209<span class="o">]</span>
succeeded <span class="k">in</span> <span class="m">3</span>.588768539018929s: <span class="s1">'1755cd30-03f5-4d8a-8d92-fa5b1853a209'</span>
</pre></div>
<p><strong>Tests unitaires</strong></p>
<p>Pour lancer des tests unitaires sur tes tâches dans ton projet Django, tu peux utiliser le paramètre suivant dans tes <em>settings</em>:</p>
<div class="highlight"><pre><span></span><span class="n">CELERY_ALWAYS_EAGER</span> <span class="o">=</span> <span class="kc">True</span>
</pre></div>
<p>Ça permet de tester les tâches Celery de manière synchrone et sans <em>broker</em> (il utilise un genre de <em>broker</em> en mémoire).</p>
<p><strong>Déploiement avec init.d</strong></p>
<p>Il existe <a class="reference external" href="https://github.com/celery/celery/tree/master/extra">un dépôt d'helpers</a> pour déployer avec init.d ou systemd. Par exemple pour init.d :</p>
<ul class="simple">
<li>Mettre le fichier <em>celeryd</em> du dépôt d'<em>helpers</em> dans <em>/etc/init.d</em>.</li>
<li>Mettre la configuration suivante dans /etc/default/celeryd:</li>
</ul>
<div class="highlight"><pre><span></span><span class="nb">export</span> <span class="nv">DJANGO_SETTINGS_MODULE</span><span class="o">=</span><span class="s2">"myproject.settings"</span>
<span class="nv">CELERYD_NODES</span><span class="o">=</span><span class="s2">"worker1"</span>
<span class="nv">CELERY_BIN</span><span class="o">=</span><span class="s2">"/home/myuser/.virtualenvs/myproject/bin/celery"</span>
<span class="nv">CELERY_APP</span><span class="o">=</span><span class="s2">"myproject"</span>
<span class="nv">CELERYD_CHDIR</span><span class="o">=</span><span class="s2">"/home/myuser/myproject"</span>
<span class="nv">CELERYD_OPTS</span><span class="o">=</span><span class="s2">"--time-limit=300 --concurrency=8"</span>
<span class="nv">CELERYD_LOG_FILE</span><span class="o">=</span><span class="s2">"/var/log/celery/%N.log"</span>
<span class="nv">CELERYD_PID_FILE</span><span class="o">=</span><span class="s2">"/var/run/celery/%N.pid"</span>
<span class="nv">CELERYD_USER</span><span class="o">=</span><span class="s2">"myuser"</span>
<span class="nv">CELERYD_GROUP</span><span class="o">=</span><span class="s2">"mygroup"</span>
<span class="nv">CELERY_CREATE_DIRS</span><span class="o">=</span><span class="m">1</span>
</pre></div>
<p><strong>Monitoring - Exemples</strong></p>
<ul class="simple">
<li>Voir le résultat d'une tâche :</li>
</ul>
<div class="highlight"><pre><span></span>celery -A myproject result -t tasks.add 4e196aa4-0141-4601-8138-7aa33db0f577
</pre></div>
<ul class="simple">
<li>Voir liste des workers actifs :</li>
</ul>
<div class="highlight"><pre><span></span>celery -A myproject status
</pre></div>
<ul class="simple">
<li>Voir les tâches actives :</li>
</ul>
<div class="highlight"><pre><span></span>celery -A myproject inspect active
</pre></div>
<ul class="simple">
<li>Voir les statistiques des workers :</li>
</ul>
<div class="highlight"><pre><span></span>celery -A myproject inspect stats
</pre></div>
<ul class="simple">
<li>Sinon, il existe le projet <a class="reference external" href="https://github.com/mher/flower">flower</a> pour monitorer et administrer les workers et les tâches via une appli web.</li>
</ul>
<p><strong>En vrac</strong></p>
<p>Quelques tips en vrac:</p>
<ul class="simple">
<li>Cron-like :</li>
</ul>
<div class="highlight"><pre><span></span>@periodic_task<span class="o">(</span><span class="nv">run_every</span><span class="o">=</span>crontab<span class="o">(</span><span class="nv">hour</span><span class="o">=</span><span class="s1">'5,13,23'</span>, <span class="nv">minute</span><span class="o">=</span><span class="m">30</span>, <span class="nv">day_of_week</span><span class="o">=</span><span class="s1">'monday'</span><span class="o">))</span>
def mytask<span class="o">()</span>:
...
</pre></div>
<ul class="simple">
<li>Sqlite n'aime pas les accès concurrents</li>
<li>Purger les tâches en attentes :</li>
</ul>
<div class="highlight"><pre><span></span>celery -A proj purge
</pre></div>
<ul class="simple">
<li>Récupérer le statut d'une tâche :</li>
</ul>
<div class="highlight"><pre><span></span><span class="nv">result</span> <span class="o">=</span> my_task.AsyncResult<span class="o">(</span>task_id<span class="o">)</span>
result.state
</pre></div>
<ul class="simple">
<li>Attendre le résultat d'une tâche :</li>
</ul>
<div class="highlight"><pre><span></span><span class="nv">result</span> <span class="o">=</span> my_task.AsyncResult<span class="o">(</span>task_id<span class="o">)</span>
result.get<span class="o">()</span>
</pre></div>
<ul class="simple">
<li>Appeler une tâche par son nom depuis une autre machine :</li>
</ul>
<div class="highlight"><pre><span></span>from celery import Celery
<span class="nv">celery</span> <span class="o">=</span> Celery<span class="o">()</span>
celery.config_from_object<span class="o">(</span><span class="s1">'celeryconfig'</span><span class="o">)</span>
celery.send_task<span class="o">(</span><span class="s1">'tasks.add'</span>, <span class="o">(</span><span class="m">2</span>,2<span class="o">))</span>
</pre></div>
<ul class="simple">
<li>Appeler une tâches via HTTP :</li>
</ul>
<div class="highlight"><pre><span></span>from celery.task.http import URL
<span class="nv">res</span> <span class="o">=</span> URL<span class="o">(</span><span class="s1">'http://example.com/multiply'</span><span class="o">)</span>.get_async<span class="o">(</span><span class="nv">x</span><span class="o">=</span><span class="m">10</span>, <span class="nv">y</span><span class="o">=</span><span class="m">10</span><span class="o">)</span>
</pre></div>
<ul class="simple">
<li>On peut appeler une tâche dans une tâche !</li>
<li>Pour l'optimisation, la sécurité, les extensions, la concurrence, voir la <a class="reference external" href="http://docs.celeryproject.org/en/latest/index.html">doc officielle</a>.</li>
</ul>
Découverte du message broker RabbitMQ2017-09-26T00:00:00+02:002017-09-26T00:00:00+02:00Morgantag:dotmobo.github.io,2017-09-26:/rabbitmq.html<p class="first last">Découverte du message broker RabbitMQ</p>
<img alt="RabbitMQ" class="align-right" src="./images/rabbitmq_logo.png" style="width: 500px;" />
<p><a class="reference external" href="https://www.rabbitmq.com/">RabbitMQ</a> est ce qu'on appelle un <em>message broker</em>.</p>
<p>Grosso-modo, ça sert à gérer des files de messages. Il permet ainsi de reçevoir et de transmettre des messages de différentes
manières.</p>
<p>Je te le recommande fortement car c'est un outil léger, très robuste, performant et simple d'utilisation.</p>
<p>Il existe depuis plus de 10 ans maintenant et est utilisé dans de grosses entreprises comme Ford, Instagram ou le New York Times.</p>
<p>S'il fallait schématiser ce que représenterait Rabbitmq dans l'IRL, ça serait le bureau de poste, la boite aux lettres ainsi que le facteur !</p>
<div class="section" id="communication">
<h2>Communication</h2>
<p>Il utilise un protocole de communication appelé <strong>AMQP</strong> (Advanced Message Queuing Protocol), qui permet de standardiser les échanges.
Ce protocole provient d'un consortium entre différentes entreprises tel que JPMorgan, Microsoft, Red Hat et Cisco.</p>
<p>Il en existe de nombreuses implémentations dans plein de langages, comme en Python avec <a class="reference external" href="https://github.com/pika/pika">Pika</a>, en Java,
en Php, en Javascript, en Ruby, etc...</p>
</div>
<div class="section" id="vocabulaire">
<h2>Vocabulaire</h2>
<p>Si tu fouilles un peu dans le doc officielle, tu remarqueras l'utilisation de certains vocabulaires.</p>
<p>Histoire de ne pas être complètement perdu, voici les plus importants :</p>
<ul class="simple">
<li><strong>Producer</strong>: celui qui envoie des messages <img alt="producer" src="./images/producer.png" /></li>
<li><strong>Queue</strong>: file qui stocke les messages <img alt="queue" src="./images/queue.png" /></li>
<li><strong>Consumer</strong>: celui qui reçoit les messages <img alt="consumer" src="./images/consumer.png" /></li>
<li><strong>Exchange</strong>: là où on peut déposer les messages <img alt="exchange" src="./images/exchange.png" /></li>
</ul>
</div>
<div class="section" id="installation">
<h2>Installation</h2>
<p>Sur Ubuntu, Rabbitmq est présent dans les dépôts officiels, donc rien de plus simple à installer:</p>
<div class="highlight"><pre><span></span><span class="c1"># Install the package</span>
apt-get install -y rabbitmq-server
</pre></div>
<p>À l'aide de <strong>rabbitmqctl</strong>, tu vas pouvoir ajouter des utilisateurs, gérer les permissions, créer des <em>Virtual Hosts</em>
(oui, c'est bien le même concept que pour Apache), effacer des utilisateurs, etc...</p>
<div class="highlight"><pre><span></span><span class="c1"># Add rabbitmq user</span>
rabbitmqctl add_user myuser mypassword
<span class="c1"># Add rabbitmq vhost</span>
rabbitmqctl add_vhost myproject
<span class="c1"># Add a tag to the user</span>
rabbitmqctl set_user_tags myuser management
<span class="c1"># Add vhost all permissions (conf, read, write) to the user</span>
rabbitmqctl set_permissions -p myproject myuser <span class="s2">".*"</span> <span class="s2">".*"</span> <span class="s2">".*"</span>
<span class="c1"># Delete the guest user</span>
rabbitmqctl delete_user guest
<span class="c1"># Check user authentication</span>
rabbitmqctl authenticate_user myuser mypassword
</pre></div>
<p>Ici, j'ai volontairement effacé l'utilisateur <em>guest</em> qui est activé par défaut.</p>
</div>
<div class="section" id="administration">
<h2>Administration</h2>
<p>Tu peux administrer Rabbitmq de deux manières:</p>
<ul class="simple">
<li>Via une interface web disponible sur <em>http://server-name:15672/</em> en activant le plugin <em>management</em>:</li>
</ul>
<div class="highlight"><pre><span></span>rabbitmq-plugins <span class="nb">enable</span> rabbitmq_management
</pre></div>
<ul class="simple">
<li>ou en ligne de commande via <strong>rabbitmqctl</strong>:</li>
</ul>
<div class="highlight"><pre><span></span><span class="c1"># users</span>
rabbitmqctl list_users
rabbitmqctl delete_user <username>
rabbitmqctl change_password <username> <newpassword>
<span class="c1"># queue</span>
rabbitmqctl list_queues -p myproject
rabbitmqctl purge_queue queue
rabbitmqctl list_connections
rabbitmqctl list_channels
rabbitmqctl list_consumers
rabbitmqctl list_exchanges
<span class="c1"># vhost</span>
rabbitmqctl list_vhosts
rabbitmqctl list_permissions -p myproject
</pre></div>
</div>
<div class="section" id="installation-du-client-python">
<h2>Installation du client Python</h2>
<p>Pour installer le client Python Pika, tu utilises pip comme d'hab':</p>
<div class="highlight"><pre><span></span>pip install pika
</pre></div>
</div>
<div class="section" id="scenarios">
<h2>Scénarios</h2>
<p>Voyons maintenant les différents types de scénarios possibles pour son usage.</p>
<div class="section" id="hello-world">
<h3>Hello World</h3>
<p><img alt="helloworld" src="./images/python-one-overall.png" /></p>
<ul class="simple">
<li>Le <em>producer</em> envoie des messages dans la <em>queue</em> hello.</li>
<li>Le <em>consumer</em> reçoit les messages de cette <em>queue</em>.</li>
</ul>
<p>Le code du <em>producer</em> <strong>send.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">pika</span>
<span class="n">connection</span> <span class="o">=</span> <span class="n">pika</span><span class="o">.</span><span class="n">BlockingConnection</span><span class="p">(</span><span class="n">pika</span><span class="o">.</span><span class="n">ConnectionParameters</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s1">'localhost'</span><span class="p">))</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">channel</span><span class="p">()</span>
<span class="n">channel</span><span class="o">.</span><span class="n">queue_declare</span><span class="p">(</span><span class="n">queue</span><span class="o">=</span><span class="s1">'hello'</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">basic_publish</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">''</span><span class="p">,</span>
<span class="n">routing_key</span><span class="o">=</span><span class="s1">'hello'</span><span class="p">,</span>
<span class="n">body</span><span class="o">=</span><span class="s1">'Hello World!'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [x] Sent 'Hello World!'"</span><span class="p">)</span>
<span class="n">connection</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</pre></div>
<p>Le code du <em>consumer</em> <strong>receive.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">pika</span>
<span class="n">connection</span> <span class="o">=</span> <span class="n">pika</span><span class="o">.</span><span class="n">BlockingConnection</span><span class="p">(</span><span class="n">pika</span><span class="o">.</span><span class="n">ConnectionParameters</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s1">'localhost'</span><span class="p">))</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">channel</span><span class="p">()</span>
<span class="n">channel</span><span class="o">.</span><span class="n">queue_declare</span><span class="p">(</span><span class="n">queue</span><span class="o">=</span><span class="s1">'hello'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">callback</span><span class="p">(</span><span class="n">ch</span><span class="p">,</span> <span class="n">method</span><span class="p">,</span> <span class="n">properties</span><span class="p">,</span> <span class="n">body</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [x] Received </span><span class="si">%r</span><span class="s2">"</span> <span class="o">%</span> <span class="n">body</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">basic_consume</span><span class="p">(</span><span class="n">callback</span><span class="p">,</span>
<span class="n">queue</span><span class="o">=</span><span class="s1">'hello'</span><span class="p">,</span>
<span class="n">no_ack</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">' [*] Waiting for messages. To exit press CTRL+C'</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">start_consuming</span><span class="p">()</span>
</pre></div>
<p>Et au final, tu as comme résultat:</p>
<div class="highlight"><pre><span></span>python receive.py
<span class="c1"># => [*] Waiting for messages. To exit press CTRL+C</span>
<span class="c1"># => [x] Received 'Hello World!'</span>
</pre></div>
<div class="highlight"><pre><span></span>python send.py
<span class="c1"># => [x] Sent 'Hello World!'</span>
</pre></div>
</div>
<div class="section" id="work-queues">
<h3>Work queues</h3>
<p><img alt="workqueues" src="./images/python-two.png" /></p>
<ul class="simple">
<li>Le <em>producer</em> envoie des jobs à effectuer dans une <em>queue</em>.</li>
<li>Plusieurs <em>consumers</em> se répartissent les tâches.</li>
<li>C'est utile pour des tâches longues (encodage, importation, copies).</li>
</ul>
<p>Le code du <em>producer</em> <strong>new_task.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">pika</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="n">connection</span> <span class="o">=</span> <span class="n">pika</span><span class="o">.</span><span class="n">BlockingConnection</span><span class="p">(</span><span class="n">pika</span><span class="o">.</span><span class="n">ConnectionParameters</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s1">'localhost'</span><span class="p">))</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">channel</span><span class="p">()</span>
<span class="n">channel</span><span class="o">.</span><span class="n">queue_declare</span><span class="p">(</span><span class="n">queue</span><span class="o">=</span><span class="s1">'task_queue'</span><span class="p">,</span> <span class="n">durable</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">message</span> <span class="o">=</span> <span class="s1">' '</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">:])</span> <span class="ow">or</span> <span class="s2">"Hello World!"</span>
<span class="n">channel</span><span class="o">.</span><span class="n">basic_publish</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">''</span><span class="p">,</span>
<span class="n">routing_key</span><span class="o">=</span><span class="s1">'task_queue'</span><span class="p">,</span>
<span class="n">body</span><span class="o">=</span><span class="n">message</span><span class="p">,</span>
<span class="n">properties</span><span class="o">=</span><span class="n">pika</span><span class="o">.</span><span class="n">BasicProperties</span><span class="p">(</span>
<span class="n">delivery_mode</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="c1"># make message persistent</span>
<span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [x] Sent </span><span class="si">%r</span><span class="s2">"</span> <span class="o">%</span> <span class="n">message</span><span class="p">)</span>
<span class="n">connection</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</pre></div>
<p>Le code du <em>consumer</em> <strong>worker.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">pika</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="n">connection</span> <span class="o">=</span> <span class="n">pika</span><span class="o">.</span><span class="n">BlockingConnection</span><span class="p">(</span><span class="n">pika</span><span class="o">.</span><span class="n">ConnectionParameters</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s1">'localhost'</span><span class="p">))</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">channel</span><span class="p">()</span>
<span class="n">channel</span><span class="o">.</span><span class="n">queue_declare</span><span class="p">(</span><span class="n">queue</span><span class="o">=</span><span class="s1">'task_queue'</span><span class="p">,</span> <span class="n">durable</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">' [*] Waiting for messages. To exit press CTRL+C'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">callback</span><span class="p">(</span><span class="n">ch</span><span class="p">,</span> <span class="n">method</span><span class="p">,</span> <span class="n">properties</span><span class="p">,</span> <span class="n">body</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [x] Received </span><span class="si">%r</span><span class="s2">"</span> <span class="o">%</span> <span class="n">body</span><span class="p">)</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="n">body</span><span class="o">.</span><span class="n">count</span><span class="p">(</span><span class="sa">b</span><span class="s1">'.'</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [x] Done"</span><span class="p">)</span>
<span class="n">ch</span><span class="o">.</span><span class="n">basic_ack</span><span class="p">(</span><span class="n">delivery_tag</span> <span class="o">=</span> <span class="n">method</span><span class="o">.</span><span class="n">delivery_tag</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">basic_qos</span><span class="p">(</span><span class="n">prefetch_count</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">basic_consume</span><span class="p">(</span><span class="n">callback</span><span class="p">,</span>
<span class="n">queue</span><span class="o">=</span><span class="s1">'task_queue'</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">start_consuming</span><span class="p">()</span>
</pre></div>
<p>Il suffit alors d'exécuter les fichiers pour simuler le fonctionnement de la file de jobs.</p>
</div>
<div class="section" id="publish-subscribe">
<h3>Publish/Subscribe</h3>
<p><img alt="pubsub" src="./images/python-three-overall.png" /></p>
<ul class="simple">
<li>Le <em>producer</em> veut délivrer un message à plusieurs <em>consumers</em>.</li>
<li>À l'inverse du scénario précédent, tous les <em>consumers</em> recoivent le message.</li>
<li>Le <em>producer</em> envoie son message à l'<em>exchange</em> qui se chargera de le délivrer aux différentes files.</li>
<li>L'<em>exchange</em> de type <em>fanout</em> délivre le message à toutes les files.</li>
</ul>
<p>Le code du <em>producer</em> <strong>emit_log.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">pika</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="n">connection</span> <span class="o">=</span> <span class="n">pika</span><span class="o">.</span><span class="n">BlockingConnection</span><span class="p">(</span><span class="n">pika</span><span class="o">.</span><span class="n">ConnectionParameters</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s1">'localhost'</span><span class="p">))</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">channel</span><span class="p">()</span>
<span class="n">channel</span><span class="o">.</span><span class="n">exchange_declare</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">'logs'</span><span class="p">,</span>
<span class="n">exchange_type</span><span class="o">=</span><span class="s1">'fanout'</span><span class="p">)</span>
<span class="n">message</span> <span class="o">=</span> <span class="s1">' '</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">:])</span> <span class="ow">or</span> <span class="s2">"info: Hello World!"</span>
<span class="n">channel</span><span class="o">.</span><span class="n">basic_publish</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">'logs'</span><span class="p">,</span>
<span class="n">routing_key</span><span class="o">=</span><span class="s1">''</span><span class="p">,</span>
<span class="n">body</span><span class="o">=</span><span class="n">message</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [x] Sent </span><span class="si">%r</span><span class="s2">"</span> <span class="o">%</span> <span class="n">message</span><span class="p">)</span>
<span class="n">connection</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</pre></div>
<p>Le code du <em>consumer</em> <strong>receive_logs.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">pika</span>
<span class="n">connection</span> <span class="o">=</span> <span class="n">pika</span><span class="o">.</span><span class="n">BlockingConnection</span><span class="p">(</span><span class="n">pika</span><span class="o">.</span><span class="n">ConnectionParameters</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s1">'localhost'</span><span class="p">))</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">channel</span><span class="p">()</span>
<span class="n">channel</span><span class="o">.</span><span class="n">exchange_declare</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">'logs'</span><span class="p">,</span>
<span class="n">exchange_type</span><span class="o">=</span><span class="s1">'fanout'</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">channel</span><span class="o">.</span><span class="n">queue_declare</span><span class="p">(</span><span class="n">exclusive</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">queue_name</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="n">method</span><span class="o">.</span><span class="n">queue</span>
<span class="n">channel</span><span class="o">.</span><span class="n">queue_bind</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">'logs'</span><span class="p">,</span>
<span class="n">queue</span><span class="o">=</span><span class="n">queue_name</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">' [*] Waiting for logs. To exit press CTRL+C'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">callback</span><span class="p">(</span><span class="n">ch</span><span class="p">,</span> <span class="n">method</span><span class="p">,</span> <span class="n">properties</span><span class="p">,</span> <span class="n">body</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [x] </span><span class="si">%r</span><span class="s2">"</span> <span class="o">%</span> <span class="n">body</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">basic_consume</span><span class="p">(</span><span class="n">callback</span><span class="p">,</span>
<span class="n">queue</span><span class="o">=</span><span class="n">queue_name</span><span class="p">,</span>
<span class="n">no_ack</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">start_consuming</span><span class="p">()</span>
</pre></div>
<p>Comme avant, tu lances les deux fichiers pour tester.</p>
</div>
<div class="section" id="routing">
<h3>Routing</h3>
<p><img alt="routing" src="./images/direct-exchange.png" /></p>
<ul class="simple">
<li>L'<em>exchange</em> de type <em>direct</em> redirige automatiquement dans la bonne file en fonction d'une clé.</li>
<li>C'est utile pour gérer des logs par exemple.</li>
</ul>
<p>Le code du <em>producer</em> <strong>emit_log_direct.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">pika</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="n">connection</span> <span class="o">=</span> <span class="n">pika</span><span class="o">.</span><span class="n">BlockingConnection</span><span class="p">(</span><span class="n">pika</span><span class="o">.</span><span class="n">ConnectionParameters</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s1">'localhost'</span><span class="p">))</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">channel</span><span class="p">()</span>
<span class="n">channel</span><span class="o">.</span><span class="n">exchange_declare</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">'direct_logs'</span><span class="p">,</span>
<span class="n">exchange_type</span><span class="o">=</span><span class="s1">'direct'</span><span class="p">)</span>
<span class="n">severity</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">></span> <span class="mi">2</span> <span class="k">else</span> <span class="s1">'info'</span>
<span class="n">message</span> <span class="o">=</span> <span class="s1">' '</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">:])</span> <span class="ow">or</span> <span class="s1">'Hello World!'</span>
<span class="n">channel</span><span class="o">.</span><span class="n">basic_publish</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">'direct_logs'</span><span class="p">,</span>
<span class="n">routing_key</span><span class="o">=</span><span class="n">severity</span><span class="p">,</span>
<span class="n">body</span><span class="o">=</span><span class="n">message</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [x] Sent </span><span class="si">%r</span><span class="s2">:</span><span class="si">%r</span><span class="s2">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">severity</span><span class="p">,</span> <span class="n">message</span><span class="p">))</span>
<span class="n">connection</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</pre></div>
<p>Le code du <em>consumer</em> <strong>receive_logs_direct.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">pika</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="n">connection</span> <span class="o">=</span> <span class="n">pika</span><span class="o">.</span><span class="n">BlockingConnection</span><span class="p">(</span><span class="n">pika</span><span class="o">.</span><span class="n">ConnectionParameters</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s1">'localhost'</span><span class="p">))</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">channel</span><span class="p">()</span>
<span class="n">channel</span><span class="o">.</span><span class="n">exchange_declare</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">'direct_logs'</span><span class="p">,</span>
<span class="n">exchange_type</span><span class="o">=</span><span class="s1">'direct'</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">channel</span><span class="o">.</span><span class="n">queue_declare</span><span class="p">(</span><span class="n">exclusive</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">queue_name</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="n">method</span><span class="o">.</span><span class="n">queue</span>
<span class="n">severities</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">severities</span><span class="p">:</span>
<span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">"Usage: </span><span class="si">%s</span><span class="s2"> [info] [warning] [error]</span><span class="se">\n</span><span class="s2">"</span> <span class="o">%</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">for</span> <span class="n">severity</span> <span class="ow">in</span> <span class="n">severities</span><span class="p">:</span>
<span class="n">channel</span><span class="o">.</span><span class="n">queue_bind</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">'direct_logs'</span><span class="p">,</span>
<span class="n">queue</span><span class="o">=</span><span class="n">queue_name</span><span class="p">,</span>
<span class="n">routing_key</span><span class="o">=</span><span class="n">severity</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">' [*] Waiting for logs. To exit press CTRL+C'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">callback</span><span class="p">(</span><span class="n">ch</span><span class="p">,</span> <span class="n">method</span><span class="p">,</span> <span class="n">properties</span><span class="p">,</span> <span class="n">body</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [x] </span><span class="si">%r</span><span class="s2">:</span><span class="si">%r</span><span class="s2">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">method</span><span class="o">.</span><span class="n">routing_key</span><span class="p">,</span> <span class="n">body</span><span class="p">))</span>
<span class="n">channel</span><span class="o">.</span><span class="n">basic_consume</span><span class="p">(</span><span class="n">callback</span><span class="p">,</span>
<span class="n">queue</span><span class="o">=</span><span class="n">queue_name</span><span class="p">,</span>
<span class="n">no_ack</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">start_consuming</span><span class="p">()</span>
</pre></div>
<p>Tu t'attends alors à reçevoir des messages de type <em>info</em>, <em>warning</em> et <em>error</em>:</p>
<div class="highlight"><pre><span></span>python receive_logs_direct.py info warning error
<span class="c1"># => [*] Waiting for logs. To exit press CTRL+C</span>
</pre></div>
<p>Par exemple, pour émettre un message de type <em>error</em>:</p>
<div class="highlight"><pre><span></span>python emit_log_direct.py error <span class="s2">"Run. Run. Or it will explode."</span>
<span class="c1"># => [x] Sent 'error':'Run. Run. Or it will explode.'</span>
</pre></div>
</div>
<div class="section" id="topics">
<h3>Topics</h3>
<p><img alt="topics" src="./images/python-five.png" /></p>
<ul class="simple">
<li>L'<em>exchange</em> de type <em>topic</em> permet de rediriger sur la bonne file en fonction de critères multiples.</li>
<li>Le message avec la clé de <strong>routage quick.orange.rabbit</strong> ira dans les 2 files.</li>
</ul>
<p>Le code du <em>producer</em> <strong>emit_log_topic.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">pika</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="n">connection</span> <span class="o">=</span> <span class="n">pika</span><span class="o">.</span><span class="n">BlockingConnection</span><span class="p">(</span><span class="n">pika</span><span class="o">.</span><span class="n">ConnectionParameters</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s1">'localhost'</span><span class="p">))</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">channel</span><span class="p">()</span>
<span class="n">channel</span><span class="o">.</span><span class="n">exchange_declare</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">'topic_logs'</span><span class="p">,</span>
<span class="n">exchange_type</span><span class="o">=</span><span class="s1">'topic'</span><span class="p">)</span>
<span class="n">routing_key</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">></span> <span class="mi">2</span> <span class="k">else</span> <span class="s1">'anonymous.info'</span>
<span class="n">message</span> <span class="o">=</span> <span class="s1">' '</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">:])</span> <span class="ow">or</span> <span class="s1">'Hello World!'</span>
<span class="n">channel</span><span class="o">.</span><span class="n">basic_publish</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">'topic_logs'</span><span class="p">,</span>
<span class="n">routing_key</span><span class="o">=</span><span class="n">routing_key</span><span class="p">,</span>
<span class="n">body</span><span class="o">=</span><span class="n">message</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [x] Sent </span><span class="si">%r</span><span class="s2">:</span><span class="si">%r</span><span class="s2">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">routing_key</span><span class="p">,</span> <span class="n">message</span><span class="p">))</span>
<span class="n">connection</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</pre></div>
<p>Le code du <em>consumer</em> <strong>receive_logs_topic.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">pika</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="n">connection</span> <span class="o">=</span> <span class="n">pika</span><span class="o">.</span><span class="n">BlockingConnection</span><span class="p">(</span><span class="n">pika</span><span class="o">.</span><span class="n">ConnectionParameters</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s1">'localhost'</span><span class="p">))</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">channel</span><span class="p">()</span>
<span class="n">channel</span><span class="o">.</span><span class="n">exchange_declare</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">'topic_logs'</span><span class="p">,</span>
<span class="n">exchange_type</span><span class="o">=</span><span class="s1">'topic'</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">channel</span><span class="o">.</span><span class="n">queue_declare</span><span class="p">(</span><span class="n">exclusive</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">queue_name</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="n">method</span><span class="o">.</span><span class="n">queue</span>
<span class="n">binding_keys</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">binding_keys</span><span class="p">:</span>
<span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">"Usage: </span><span class="si">%s</span><span class="s2"> [binding_key]...</span><span class="se">\n</span><span class="s2">"</span> <span class="o">%</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">for</span> <span class="n">binding_key</span> <span class="ow">in</span> <span class="n">binding_keys</span><span class="p">:</span>
<span class="n">channel</span><span class="o">.</span><span class="n">queue_bind</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">'topic_logs'</span><span class="p">,</span>
<span class="n">queue</span><span class="o">=</span><span class="n">queue_name</span><span class="p">,</span>
<span class="n">routing_key</span><span class="o">=</span><span class="n">binding_key</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">' [*] Waiting for logs. To exit press CTRL+C'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">callback</span><span class="p">(</span><span class="n">ch</span><span class="p">,</span> <span class="n">method</span><span class="p">,</span> <span class="n">properties</span><span class="p">,</span> <span class="n">body</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [x] </span><span class="si">%r</span><span class="s2">:</span><span class="si">%r</span><span class="s2">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">method</span><span class="o">.</span><span class="n">routing_key</span><span class="p">,</span> <span class="n">body</span><span class="p">))</span>
<span class="n">channel</span><span class="o">.</span><span class="n">basic_consume</span><span class="p">(</span><span class="n">callback</span><span class="p">,</span>
<span class="n">queue</span><span class="o">=</span><span class="n">queue_name</span><span class="p">,</span>
<span class="n">no_ack</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">start_consuming</span><span class="p">()</span>
</pre></div>
<p>Tu peux maintenant utiliser les topics de différentes manières :</p>
<div class="highlight"><pre><span></span><span class="c1"># To receive all the logs run:</span>
python receive_logs_topic.py <span class="s2">"#"</span>
<span class="c1"># To receive all logs from the facility "kern":</span>
python receive_logs_topic.py <span class="s2">"kern.*"</span>
<span class="c1"># Or if you want to hear only about "critical" logs:</span>
python receive_logs_topic.py <span class="s2">"*.critical"</span>
<span class="c1"># You can create multiple bindings:</span>
python receive_logs_topic.py <span class="s2">"kern.*"</span> <span class="s2">"*.critical"</span>
<span class="c1"># And to emit a log with a routing key "kern.critical" type:</span>
python emit_log_topic.py <span class="s2">"kern.critical"</span> <span class="s2">"A critical kernel error"</span>
</pre></div>
</div>
<div class="section" id="rpc">
<h3>RPC</h3>
<p><img alt="rpc" src="./images/python-six.png" /></p>
<ul class="simple">
<li>RPC permet d'exécuter une fonction distante en mode <em>Request/Reply</em>.</li>
<li>Le client envoie une <em>request</em> avec une clé unique et le nom de la file de retour.</li>
<li>Le serveur attend les requêtes, exécute la fonction et retourne la réponse.</li>
</ul>
<p>Le code de la partie <em>server</em> <em>rpc_server.py</em> :</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">pika</span>
<span class="n">connection</span> <span class="o">=</span> <span class="n">pika</span><span class="o">.</span><span class="n">BlockingConnection</span><span class="p">(</span><span class="n">pika</span><span class="o">.</span><span class="n">ConnectionParameters</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s1">'localhost'</span><span class="p">))</span>
<span class="n">channel</span> <span class="o">=</span> <span class="n">connection</span><span class="o">.</span><span class="n">channel</span><span class="p">()</span>
<span class="n">channel</span><span class="o">.</span><span class="n">queue_declare</span><span class="p">(</span><span class="n">queue</span><span class="o">=</span><span class="s1">'rpc_queue'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">fib</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="k">if</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="k">return</span> <span class="mi">0</span>
<span class="k">elif</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">return</span> <span class="mi">1</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="n">fib</span><span class="p">(</span><span class="n">n</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">fib</span><span class="p">(</span><span class="n">n</span><span class="o">-</span><span class="mi">2</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">on_request</span><span class="p">(</span><span class="n">ch</span><span class="p">,</span> <span class="n">method</span><span class="p">,</span> <span class="n">props</span><span class="p">,</span> <span class="n">body</span><span class="p">):</span>
<span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">body</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [.] fib(</span><span class="si">%s</span><span class="s2">)"</span> <span class="o">%</span> <span class="n">n</span><span class="p">)</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">fib</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
<span class="n">ch</span><span class="o">.</span><span class="n">basic_publish</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">''</span><span class="p">,</span>
<span class="n">routing_key</span><span class="o">=</span><span class="n">props</span><span class="o">.</span><span class="n">reply_to</span><span class="p">,</span>
<span class="n">properties</span><span class="o">=</span><span class="n">pika</span><span class="o">.</span><span class="n">BasicProperties</span><span class="p">(</span><span class="n">correlation_id</span> <span class="o">=</span> \
<span class="n">props</span><span class="o">.</span><span class="n">correlation_id</span><span class="p">),</span>
<span class="n">body</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">response</span><span class="p">))</span>
<span class="n">ch</span><span class="o">.</span><span class="n">basic_ack</span><span class="p">(</span><span class="n">delivery_tag</span> <span class="o">=</span> <span class="n">method</span><span class="o">.</span><span class="n">delivery_tag</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">basic_qos</span><span class="p">(</span><span class="n">prefetch_count</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">basic_consume</span><span class="p">(</span><span class="n">on_request</span><span class="p">,</span> <span class="n">queue</span><span class="o">=</span><span class="s1">'rpc_queue'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [x] Awaiting RPC requests"</span><span class="p">)</span>
<span class="n">channel</span><span class="o">.</span><span class="n">start_consuming</span><span class="p">()</span>
</pre></div>
<p>Le code de la partie <em>cliente</em> <em>rpc_client.py</em> :</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">pika</span>
<span class="kn">import</span> <span class="nn">uuid</span>
<span class="k">class</span> <span class="nc">FibonacciRpcClient</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">connection</span> <span class="o">=</span> <span class="n">pika</span><span class="o">.</span><span class="n">BlockingConnection</span><span class="p">(</span><span class="n">pika</span><span class="o">.</span><span class="n">ConnectionParameters</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s1">'localhost'</span><span class="p">))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">channel</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">channel</span><span class="p">()</span>
<span class="n">result</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">channel</span><span class="o">.</span><span class="n">queue_declare</span><span class="p">(</span><span class="n">exclusive</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">callback_queue</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="n">method</span><span class="o">.</span><span class="n">queue</span>
<span class="bp">self</span><span class="o">.</span><span class="n">channel</span><span class="o">.</span><span class="n">basic_consume</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">on_response</span><span class="p">,</span> <span class="n">no_ack</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="n">queue</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">callback_queue</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">on_response</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ch</span><span class="p">,</span> <span class="n">method</span><span class="p">,</span> <span class="n">props</span><span class="p">,</span> <span class="n">body</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">corr_id</span> <span class="o">==</span> <span class="n">props</span><span class="o">.</span><span class="n">correlation_id</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">response</span> <span class="o">=</span> <span class="n">body</span>
<span class="k">def</span> <span class="nf">call</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">n</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">response</span> <span class="o">=</span> <span class="kc">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">corr_id</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">uuid</span><span class="o">.</span><span class="n">uuid4</span><span class="p">())</span>
<span class="bp">self</span><span class="o">.</span><span class="n">channel</span><span class="o">.</span><span class="n">basic_publish</span><span class="p">(</span><span class="n">exchange</span><span class="o">=</span><span class="s1">''</span><span class="p">,</span>
<span class="n">routing_key</span><span class="o">=</span><span class="s1">'rpc_queue'</span><span class="p">,</span>
<span class="n">properties</span><span class="o">=</span><span class="n">pika</span><span class="o">.</span><span class="n">BasicProperties</span><span class="p">(</span>
<span class="n">reply_to</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">callback_queue</span><span class="p">,</span>
<span class="n">correlation_id</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">corr_id</span><span class="p">,</span>
<span class="p">),</span>
<span class="n">body</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>
<span class="k">while</span> <span class="bp">self</span><span class="o">.</span><span class="n">response</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">process_data_events</span><span class="p">()</span>
<span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">response</span><span class="p">)</span>
<span class="n">fibonacci_rpc</span> <span class="o">=</span> <span class="n">FibonacciRpcClient</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [x] Requesting fib(30)"</span><span class="p">)</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">fibonacci_rpc</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">" [.] Got </span><span class="si">%r</span><span class="s2">"</span> <span class="o">%</span> <span class="n">response</span><span class="p">)</span>
</pre></div>
<p>Et pour tester ça :</p>
<div class="highlight"><pre><span></span>python rpc_server.py
<span class="c1"># => [x] Awaiting RPC requests</span>
</pre></div>
<div class="highlight"><pre><span></span>python rpc_client.py
<span class="c1"># => [x] Requesting fib(30)</span>
</pre></div>
</div>
<div class="section" id="conclusion">
<h3>Conclusion</h3>
<p>Cette introduction résume la <a class="reference external" href="https://www.rabbitmq.com/getstarted.html">doc officielle</a>,
mais n'hésite pas à te plonger dedans pour plus de détail si besoin.</p>
<p>Il est possible, par exemple, d'activer la persistance des données, d'authentifier les connections via un serveur LDAP et autres.</p>
<p>Dans une prochaine partie, nous verrons comment utiliser <a class="reference external" href="http://www.celeryproject.org/">Celery</a> avec Rabbitmq.</p>
</div>
</div>
Mes librairies python indispensables2016-07-11T00:00:00+02:002016-07-11T00:00:00+02:00Morgantag:dotmobo.github.io,2016-07-11:/indispensables-python.html<p class="first last">Mes librairies python indispensables</p>
<img alt="Python" class="align-right" src="./images/python.png" />
<p>Ça y est c'est les vacances ! À toi le soleil, les plages, les cocotiers et ...
les librairies python bien sûr !</p>
<p>Du coup, j'en profite pour te un faire petit listing de mes librairies
indispensables en python. On ne sait jamais, peut-être que tu y découvriras
quelque-chose d'utile !</p>
<div class="section" id="framework-web">
<h2>Framework web</h2>
<ul class="simple">
<li><a class="reference external" href="https://www.djangoproject.com/">django</a>: framework le plus réputé, qui a l'avantage d'avoir une tonne de
support et de documentation. Indispensable pour créer des applications web complexes.</li>
<li><a class="reference external" href="http://bottlepy.org/docs/dev/index.html">bottle</a>: framework ultra minimaliste, idéal pour les applications web de petite taille.</li>
<li><a class="reference external" href="http://blog.getpelican.com/">pelican</a>: générateur de site statique utilisé par ce blog ! Tuto dispo <a class="reference external" href="http://dotmobo.github.io/pelican.html">ici</a>.</li>
</ul>
</div>
<div class="section" id="base-de-donnees">
<h2>Base de données</h2>
<ul class="simple">
<li><a class="reference external" href="http://www.sqlalchemy.org/">sqlalchemy</a>: orm python le plus avancé, tout simplement.</li>
<li><a class="reference external" href="http://docs.peewee-orm.com/">peewee</a>: orm léger et simple, parfait pour des besoins pas trop complexes.</li>
<li><a class="reference external" href="http://initd.org/psycopg/">psycopg2</a>: driver postgresql</li>
<li><a class="reference external" href="http://cx-oracle.sourceforge.net/">cx-oracle</a>: driver oracle</li>
<li><a class="reference external" href="https://pypi.python.org/pypi/tinydb">tinydb</a>: base de données ultra minimaliste. Tuto dispo <a class="reference external" href="http://dotmobo.github.io/tinydb.html">ici</a>.</li>
</ul>
</div>
<div class="section" id="http">
<h2>Http</h2>
<ul class="simple">
<li><a class="reference external" href="http://httpie.org">httpie</a>: client http en ligne de commande. Tuto dispo <a class="reference external" href="http://dotmobo.github.io/httpie.html">ici</a>.</li>
<li><a class="reference external" href="http://docs.python-requests.org/en/master/">requests</a>: librairie http la plus simple et le plus efficace.</li>
<li><a class="reference external" href="https://github.com/agrausem/britney">britney</a> et <a class="reference external" href="https://github.com/unistra/britney-utils">britney-utils</a>: client <a class="reference external" href="http://spore.github.io/">SPORE</a>, qui est une spécification de description
d'APIs REST.</li>
<li><a class="reference external" href="https://bitbucket.org/jurko/suds">suds-jurko</a>: client SOAP léger et simple d'usage.</li>
</ul>
</div>
<div class="section" id="templating">
<h2>Templating</h2>
<ul class="simple">
<li><a class="reference external" href="http://jinja.pocoo.org/">jinja2</a>: moteur de template le plus réputé, utilisé par django.</li>
<li><a class="reference external" href="http://www.makotemplates.org/">mako</a>: moteur de template ultra performant. Tuto dispo <a class="reference external" href="http://dotmobo.github.io/mako.html">ici</a>.</li>
<li><a class="reference external" href="https://github.com/audreyr/cookiecutter">cookiecutter</a>: moteur de template de projet utilisant jinja2. Tu peux jeter un oeil à <a class="reference external" href="http://sametmax.com/templates-de-projet-avec-cookiecutter/">l'article
de Sam</a> sur le sujet.
Exemples d'utilisation: <a class="reference external" href="https://github.com/unistra/simple-python-drybones">simple-python-drybones</a> et <a class="reference external" href="https://github.com/unistra/bottle-drybones">bottle-drybones</a>.</li>
</ul>
</div>
<div class="section" id="machine-learning">
<h2>Machine learning</h2>
<ul class="simple">
<li><a class="reference external" href="http://scikit-learn.org/">scikit-learn</a>: projet de machine learning qui provient du Google Summer Of Code 2007.
Google est en train d'en faire <a class="reference external" href="https://www.youtube.com/watch?v=cKxRvEZd3Mw&list=PLOU2XLYxmsIIuiBfYad6rFYQU_jL2ryal&index=6">une série du tutos sur youtube</a>
vraiment intéressante.</li>
</ul>
</div>
<div class="section" id="deploiement">
<h2>Déploiement</h2>
<ul class="simple">
<li><a class="reference external" href="http://www.fabfile.org/">fabric</a>: outil qui permet déployer des applications via ssh.</li>
<li><a class="reference external" href="https://github.com/ronnix/fabtools">fabtools</a>: ensemble de fonctionnalités pour fabric.</li>
<li><a class="reference external" href="https://github.com/unistra/pydiploy">pydiploy</a>: projet qui utilise fabric et fabtools pour automatiser le déploiement
d'applications. Pour les applications django par exemple, il permet de déployer et de configurer
toute la stack python/virtualenv/circus/chaussette/nginx.</li>
</ul>
</div>
<div class="section" id="django">
<h2>Django</h2>
<ul class="simple">
<li><a class="reference external" href="https://github.com/django-extensions/django-extensions">django-extensions</a>: extension des commandes de django.</li>
<li><a class="reference external" href="https://github.com/andreyfedoseev/django-static-precompiler">django-static-precompiler</a>: librairie qui permet d'automatiser la précompilation des fichiers
CoffeeScript, Livescript, Sass, Less et autres.</li>
<li><a class="reference external" href="http://django-crispy-forms.readthedocs.io/">django-crispy</a>: outil formidable pour créer des formulaires compatibles bootstrap. Tuto dispo <a class="reference external" href="http://dotmobo.github.io/django-crispy-forms.html">ici</a>.</li>
<li><a class="reference external" href="https://github.com/diefenbach/django-workflows">django-workflows</a> et <a class="reference external" href="https://github.com/unistra/django-workflow-activity">django-workflow-activity</a>: outils pour créer des worflows états/transitions.</li>
<li><a class="reference external" href="https://github.com/unistra/django-drybones">django-drybones</a>: template de projet django.</li>
<li><a class="reference external" href="https://github.com/yourlabs/django-autocomplete-light">django-autocomplete-light</a>: module d'autocomplétion pour les champs des formulaires.</li>
<li><a class="reference external" href="https://github.com/SmileyChris/django-countries">django-countries</a>: gestion des pays dans django. Tuto dispo <a class="reference external" href="http://dotmobo.github.io/django-countries.html">ici</a>.</li>
<li><a class="reference external" href="https://github.com/mbi/django-simple-captcha">django-simple-captcha</a>: utilisation de captchas. Tuto dispo <a class="reference external" href="http://dotmobo.github.io/django-simple-captcha.html">ici</a>.</li>
<li><a class="reference external" href="http://www.django-rest-framework.org/">django-rest-framework</a> (drf): excellente librairie pour créer des APIs REST.</li>
<li><a class="reference external" href="https://github.com/unistra/django-rest-framework-fine-permissions">django-fine-permissions</a>: gestion des permissions fines, par champs et par utilisateur,
pour drf.</li>
<li><a class="reference external" href="https://github.com/carltongibson/django-filter">django-filter</a>: gestion des filtres des querysets qui fonctionne très bien avec drf.</li>
<li><a class="reference external" href="https://github.com/unistra/django-hypnos">django-hypnos</a>: outil qui permet de générer automatiquement une api rest avec drf,
à partir d'une base de données sqlite/postgresql/mysql/oracle existante.</li>
<li><a class="reference external" href="https://pypi.python.org/pypi/django-cas-sso/">django-cas-sso</a>: client pour l'authentification CAS, qui supporte le Single-Sign-Out.</li>
<li><a class="reference external" href="https://github.com/unistra/django-ldapdb">django-ldapdb</a>: backend pour manipuler les entrées des annuaires LDAP.</li>
<li><a class="reference external" href="https://github.com/evonove/django-oauth-toolkit">django-oauth-toolkit</a>: ensemble d'outils pour oauth2.</li>
</ul>
</div>
<div class="section" id="test">
<h2>Test</h2>
<ul class="simple">
<li><a class="reference external" href="http://tox.readthedocs.io/">tox</a>: outil qui permet d'exécuter les tests unitaires sous plusieurs virtualenvs
avec des configurations différentes. Tuto dispo <a class="reference external" href="http://dotmobo.github.io/integration-continue.html">ici</a>.</li>
<li><a class="reference external" href="https://coverage.readthedocs.io">coverage</a>: outil d'analyse de la couverture du code.</li>
<li><a class="reference external" href="http://prospector.landscape.io/en/master/">prospector</a>: outil utilisé par <a class="reference external" href="https://landscape.io/">landsacape.io</a>
qui utilise les meilleurs linters python pour vérifier la qualité du code.</li>
</ul>
</div>
<div class="section" id="doc">
<h2>Doc</h2>
<ul class="simple">
<li><a class="reference external" href="http://www.sphinx-doc.org/">sphinx</a>: outil de génération de documentation à partir de fichiers reStructuredText.</li>
</ul>
</div>
<div class="section" id="wamp">
<h2>Wamp</h2>
<ul class="simple">
<li><a class="reference external" href="http://crossbar.io/">crossbar</a>: router wamp le plus avancé pour python.
Il y a <a class="reference external" href="http://sametmax.com/tag/wamp/">de nombreux articles chez Sam</a>.</li>
<li><a class="reference external" href="http://autobahn.ws/python/">autobahn</a>: implémentation du protocle wamp qui fonctionne très bien avec crossbar.</li>
</ul>
</div>
<div class="section" id="wsgi">
<h2>Wsgi</h2>
<ul class="simple">
<li><a class="reference external" href="https://circus.readthedocs.io">circus</a> + <a class="reference external" href="https://chaussette.readthedocs.io/">chaussette</a> + <a class="reference external" href="http://docs.pylonsproject.org/projects/waitress/">waitress</a>: stack wsgi complète.
Tuto dispo <a class="reference external" href="http://dotmobo.github.io/chaussette-circus.html">ici</a>.</li>
</ul>
</div>
<div class="section" id="date">
<h2>Date</h2>
<ul class="simple">
<li><a class="reference external" href="https://dateutil.readthedocs.io/">python-dateutil</a>: extension au module datetime de python.</li>
<li><a class="reference external" href="https://pypi.python.org/pypi/pytz">pytz</a>: gestion des timezones.</li>
</ul>
</div>
<div class="section" id="script">
<h2>Script</h2>
<ul class="simple">
<li><a class="reference external" href="http://docopt.org/">docopt</a>: librairie pour parser les arguments de script de manière élégante.</li>
<li><a class="reference external" href="https://ipython.org/">ipython</a>: shell python le plus avancé.</li>
<li><a class="reference external" href="https://apscheduler.readthedocs.io/">apscheduler</a>: planificateur de tâches à la manière des crons.</li>
</ul>
</div>
<div class="section" id="crypto">
<h2>Crypto</h2>
<ul class="simple">
<li><a class="reference external" href="https://www.dlitz.net/software/pycrypto/">pycrypto</a>: outil de cryptographie, pour générer des hashs en sha256 par exemple.</li>
</ul>
</div>
<div class="section" id="parsing">
<h2>Parsing</h2>
<ul class="simple">
<li><a class="reference external" href="http://lxml.de/">lxml</a>: librairie pour lire/écrire du xml.</li>
<li><a class="reference external" href="https://pypi.python.org/pypi/jsonschema">jsonschema</a>: implémentation de JSON Schema.</li>
<li><a class="reference external" href="http://pyyaml.org/">pyyaml</a>: librairie pour lire/écrire du yaml.</li>
<li><a class="reference external" href="https://pypi.python.org/pypi/reportlab">reportlab</a>: outil de production de pdf.</li>
</ul>
</div>
<div class="section" id="en-vrac">
<h2>En vrac</h2>
<ul class="simple">
<li><a class="reference external" href="https://pythonhosted.org/six/">six</a>: librairie pour la compatibilité python 2/python 3.</li>
<li><a class="reference external" href="https://toolz.readthedocs.io/">pytoolz</a>: extension d'itertools et functools.
Tuto dispo <a class="reference external" href="http://dotmobo.github.io/pytoolz.html">ici</a>.</li>
<li><a class="reference external" href="https://pypi.python.org/pypi/Unidecode">unidecode</a>: libairie très pratique permet de remplacer des caractères unicode en ascii.</li>
</ul>
<p>Bonnes découvertes et bonnes vacances !</p>
</div>
Créer un formulaire sous django avec django-crispy-forms2016-05-04T00:00:00+02:002016-05-04T00:00:00+02:00Morgantag:dotmobo.github.io,2016-05-04:/django-crispy-forms.html<p class="first last">Créer un formulaire sous django avec django-crispy-forms</p>
<img alt="Django" class="align-right" src="./images/djangopony.png" />
<p><a class="reference external" href="http://django-crispy-forms.readthedocs.io/">Django-crispy-forms</a> est une application django
qui va te permettre de construire, customiser et réutiliser tes formulaires en utilisant ton
framework CSS favori. Il permet d'éviter d'écrire une tonne de code dans les templates et applique la
philosophie <a class="reference external" href="https://fr.wikipedia.org/wiki/Ne_vous_r%C3%A9p%C3%A9tez_pas">DRY</a>.</p>
<p>Par défaut, il supporte les frameworks CSS <a class="reference external" href="http://getbootstrap.com/">bootstrap</a>,
<a class="reference external" href="http://foundation.zurb.com/">foundation</a> et <a class="reference external" href="https://github.com/draganbabic/uni-form/">uni-form</a>.</p>
<p>On va voir comment tu peux créer un formulaire d'enregistrement compatible avec bootstrap en
utilisant des onglets, <a class="reference external" href="http://dotmobo.github.io/django-simple-captcha.html">un captcha</a>,
<a class="reference external" href="http://dotmobo.github.io/django-countries.html">une liste de pays</a> et
<a class="reference external" href="https://github.com/nkunihiko/django-bootstrap3-datetimepicker">un date picker</a>.</p>
<p>Pour simplifier ce tuto, on ne va pas s'occuper de l'internationalisation. Mais
rien ne t'empêches de la mettre en place de ton côté.</p>
<div class="section" id="l-installation-et-la-configuration">
<h2>1) L'installation et la configuration</h2>
<p>C'est parti ! On suppose que tu as déjà installé python 3.5 avec virtualenvwrapper.
Tu crées donc ton environnement virtuel et tu installes les librairies requises:</p>
<div class="highlight"><pre><span></span>mkvirtualenv -p /usr/bin/python3.5 demo-django-crispy-forms
pip install <span class="nv">Django</span><span class="o">==</span><span class="m">1</span>.9.6 django-countries<span class="o">==</span><span class="m">3</span>.4.1 django-crispy-forms<span class="o">==</span><span class="m">1</span>.6.0 <span class="se">\</span>
django-simple-captcha<span class="o">==</span><span class="m">0</span>.5.1
</pre></div>
<p>La version compatible de <em>django-bootstrap3-datetimepicker</em> avec django 1.9 n'est pas encore disponible
sur pypi. Du coup, tu l'installes directement depuis github:</p>
<div class="highlight"><pre><span></span>pip install git+https://github.com/nkunihiko/django-bootstrap3-datetimepicker.git@2fa9ea5
</pre></div>
<p>Tu crées un projet django appelé <strong>demo-django-crispy-forms</strong>, qui contient une application <strong>core</strong>:</p>
<div class="highlight"><pre><span></span>django-admin startproject demo_django_crispy_forms
<span class="nb">cd</span> demo_django_crispy_forms/demo_django_crispy_forms
mkdir apps <span class="o">&&</span> <span class="nb">cd</span> apps
django-admin startapp core
</pre></div>
<p>Dans le fichier <strong>settings.py</strong> de ton projet, tu modifies/ajoutes les lignes suivantes:</p>
<div class="highlight"><pre><span></span><span class="n">LANGUAGE_CODE</span> <span class="o">=</span> <span class="s1">'fr'</span>
<span class="n">TIME_ZONE</span> <span class="o">=</span> <span class="s1">'Europe/Paris'</span>
<span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">(</span>
<span class="o">...</span>
<span class="s1">'crispy_forms'</span><span class="p">,</span>
<span class="s1">'django_countries'</span><span class="p">,</span>
<span class="s1">'bootstrap3_datetime'</span><span class="p">,</span>
<span class="s1">'captcha'</span><span class="p">,</span>
<span class="s1">'demo_django_crispy_forms.apps.core'</span>
<span class="p">)</span>
<span class="n">CRISPY_TEMPLATE_PACK</span> <span class="o">=</span> <span class="s1">'bootstrap3'</span>
</pre></div>
<p>Tu as ainsi défini la langue et le time zone, ajouté les applications installées précédemment
et utilisé le template <strong>bootstrap3</strong> pour <em>crispy-forms</em>.</p>
<p>Il te suffit de migrer la base de données et de vérifier que tout est bon pour l'instant.
Depuis le répertoire racine du projet, tu fais:</p>
<div class="highlight"><pre><span></span>./manage.py migrate <span class="o">&&</span> ./manage.py check
</pre></div>
</div>
<div class="section" id="le-modele">
<h2>2) Le modèle</h2>
<p>Maintenant que tu as fini l'installation et le paramétrage des différentes libraires,
tu vas pouvoir créer le modèle de ton inscription.</p>
<p>Dans <strong>apps/core/models.py</strong>, tu définis le modèle <strong>Registration</strong> suivant :</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>
<span class="kn">from</span> <span class="nn">django_countries.fields</span> <span class="kn">import</span> <span class="n">CountryField</span>
<span class="k">class</span> <span class="nc">Registration</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Modèle de l'inscription</span>
<span class="sd"> """</span>
<span class="n">CIVILITY_CHOICES</span> <span class="o">=</span> <span class="p">(</span>
<span class="p">(</span><span class="s1">'M.'</span><span class="p">,</span> <span class="s1">'M.'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'MME'</span><span class="p">,</span> <span class="s1">'Mme'</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">STREET_TYPE_CHOICES</span> <span class="o">=</span> <span class="p">(</span>
<span class="p">(</span><span class="s1">'Boulevard'</span><span class="p">,</span> <span class="s1">'Boulevard'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Avenue'</span><span class="p">,</span> <span class="s1">'Avenue'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Cours'</span><span class="p">,</span> <span class="s1">'Cours'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Place'</span><span class="p">,</span> <span class="s1">'Place'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Rue'</span><span class="p">,</span> <span class="s1">'Rue'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Route'</span><span class="p">,</span> <span class="s1">'Route'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Voie'</span><span class="p">,</span> <span class="s1">'Voie'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Chemin'</span><span class="p">,</span> <span class="s1">'Chemin'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Square'</span><span class="p">,</span> <span class="s1">'Square'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Impasse'</span><span class="p">,</span> <span class="s1">'Impasse'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Rond-point'</span><span class="p">,</span> <span class="s1">'Rond-point'</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'Quai'</span><span class="p">,</span> <span class="s1">'Quai'</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">civility</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">choices</span><span class="o">=</span><span class="n">CIVILITY_CHOICES</span><span class="p">,</span>
<span class="n">default</span><span class="o">=</span><span class="s1">'M.'</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Civilité"</span><span class="p">)</span>
<span class="n">birth_name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Nom de naissance"</span><span class="p">)</span>
<span class="n">last_name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">null</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Nom d'usage ou marital"</span><span class="p">)</span>
<span class="n">first_name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Prénom"</span><span class="p">)</span>
<span class="n">birth_date</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">DateField</span><span class="p">(</span><span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Date de naissance "</span><span class="p">)</span>
<span class="n">birth_place</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Ville de naissance"</span><span class="p">)</span>
<span class="n">birth_country</span> <span class="o">=</span> <span class="n">CountryField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Pays de naissance"</span><span class="p">)</span>
<span class="n">mail</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">EmailField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Mail"</span><span class="p">)</span>
<span class="n">street_type</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Type de rue"</span><span class="p">,</span>
<span class="n">choices</span><span class="o">=</span><span class="n">STREET_TYPE_CHOICES</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s1">'Rue'</span><span class="p">)</span>
<span class="n">street_number</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Numéro de rue"</span><span class="p">)</span>
<span class="n">street</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Rue"</span><span class="p">)</span>
<span class="n">comp_1</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Complément 1"</span><span class="p">,</span>
<span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">null</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">comp_2</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Complément 2"</span><span class="p">,</span>
<span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">null</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">city</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Ville"</span><span class="p">)</span>
<span class="n">zip_code</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Code postal"</span><span class="p">)</span>
<span class="n">country</span> <span class="o">=</span> <span class="n">CountryField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Pays"</span><span class="p">)</span>
<span class="n">phone</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">255</span><span class="p">,</span> <span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">null</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Téléphone"</span><span class="p">)</span>
<span class="n">comments</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">TextField</span><span class="p">(</span><span class="n">blank</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">null</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">verbose_name</span><span class="o">=</span><span class="s2">"Commentaires"</span><span class="p">)</span>
</pre></div>
<p>Pour chaque champ, <em>crispy-forms</em> va :</p>
<ul class="simple">
<li>utiliser le <strong>verbose_name</strong> comme label.</li>
<li>vérifier les paramètres <strong>blank</strong> et <strong>null</strong> pour savoir si le champ est obligatoire.</li>
<li>utiliser le type de champ pour définir le type de la balise <strong><input></strong>.</li>
<li>récupérer les valeurs du paramètre <strong>choices</strong> (si présent) pour la balise <strong><select></strong>.</li>
</ul>
<p>Enfin, tu mets à jour la base de données:</p>
<div class="highlight"><pre><span></span>./manage.py makemigrations
./manage.py migrate
</pre></div>
</div>
<div class="section" id="le-formulaire">
<h2>3) Le formulaire</h2>
<p>Place au formulaire. J'ai rajouté des commentaires directement dans le code
ci-dessous pour expliquer les différentes étapes.</p>
<p>Dans <strong>apps/core/forms.py</strong>, tu mets:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django</span> <span class="kn">import</span> <span class="n">forms</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Registration</span>
<span class="kn">from</span> <span class="nn">crispy_forms.helper</span> <span class="kn">import</span> <span class="n">FormHelper</span>
<span class="kn">from</span> <span class="nn">crispy_forms.bootstrap</span> <span class="kn">import</span> <span class="n">StrictButton</span>
<span class="kn">from</span> <span class="nn">bootstrap3_datetime.widgets</span> <span class="kn">import</span> <span class="n">DateTimePicker</span>
<span class="kn">from</span> <span class="nn">crispy_forms.layout</span> <span class="kn">import</span> <span class="n">Layout</span>
<span class="kn">from</span> <span class="nn">crispy_forms.bootstrap</span> <span class="kn">import</span> <span class="n">TabHolder</span><span class="p">,</span> <span class="n">Tab</span>
<span class="kn">from</span> <span class="nn">captcha.fields</span> <span class="kn">import</span> <span class="n">CaptchaField</span>
<span class="k">class</span> <span class="nc">RegistrationForm</span><span class="p">(</span><span class="n">forms</span><span class="o">.</span><span class="n">ModelForm</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Formulaire d'inscription</span>
<span class="sd"> """</span>
<span class="c1"># Ici, tu vas rajouter les champs supplémentaires au modèle</span>
<span class="c1"># Tu définis le captcha</span>
<span class="n">captcha</span> <span class="o">=</span> <span class="n">CaptchaField</span><span class="p">()</span>
<span class="c1"># Tu ajoutes un mail de confirmation</span>
<span class="n">confirmation_mail</span> <span class="o">=</span> <span class="n">forms</span><span class="o">.</span><span class="n">EmailField</span><span class="p">(</span><span class="n">label</span><span class="o">=</span><span class="s2">"Mail de confirmation"</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Surcharge de l'initialisation du formulaire</span>
<span class="sd"> """</span>
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="c1"># Tu modifies le label de la date de naissance pour rajouter le format</span>
<span class="bp">self</span><span class="o">.</span><span class="n">fields</span><span class="p">[</span><span class="s1">'birth_date'</span><span class="p">]</span><span class="o">.</span><span class="n">label</span> <span class="o">=</span> <span class="s2">"</span><span class="si">%s</span><span class="s2"> (JJ/MM/AAAA)"</span> <span class="o">%</span> <span class="s2">"Date de naissance"</span>
<span class="c1"># Tu utilises FormHelper pour customiser ton formulaire</span>
<span class="bp">self</span><span class="o">.</span><span class="n">helper</span> <span class="o">=</span> <span class="n">FormHelper</span><span class="p">()</span>
<span class="c1"># Tu définis l'id et la classe bootstrap de ton formulaire</span>
<span class="bp">self</span><span class="o">.</span><span class="n">helper</span><span class="o">.</span><span class="n">form_class</span> <span class="o">=</span> <span class="s1">'form-horizontal'</span>
<span class="bp">self</span><span class="o">.</span><span class="n">helper</span><span class="o">.</span><span class="n">form_id</span> <span class="o">=</span> <span class="s1">'registration-form'</span>
<span class="c1"># Tu définis la taille des labels et des champs sur la grille</span>
<span class="bp">self</span><span class="o">.</span><span class="n">helper</span><span class="o">.</span><span class="n">label_class</span> <span class="o">=</span> <span class="s1">'col-md-2'</span>
<span class="bp">self</span><span class="o">.</span><span class="n">helper</span><span class="o">.</span><span class="n">field_class</span> <span class="o">=</span> <span class="s1">'col-md-8'</span>
<span class="c1"># Tu crées l'affichage de ton formulaire</span>
<span class="bp">self</span><span class="o">.</span><span class="n">helper</span><span class="o">.</span><span class="n">layout</span> <span class="o">=</span> <span class="n">Layout</span><span class="p">(</span>
<span class="c1"># Le formulaire va contenir 3 onglets</span>
<span class="n">TabHolder</span><span class="p">(</span>
<span class="c1"># Premier onglet</span>
<span class="n">Tab</span><span class="p">(</span>
<span class="c1"># Label de l'onglet</span>
<span class="s1">'Étape 1 - Identité'</span><span class="p">,</span>
<span class="c1"># Liste des champs du modèle à afficher dans l'onglet</span>
<span class="s1">'civility'</span><span class="p">,</span>
<span class="s1">'birth_name'</span><span class="p">,</span>
<span class="s1">'last_name'</span><span class="p">,</span>
<span class="s1">'first_name'</span><span class="p">,</span>
<span class="s1">'birth_date'</span><span class="p">,</span>
<span class="s1">'birth_place'</span><span class="p">,</span>
<span class="s1">'birth_country'</span><span class="p">,</span>
<span class="c1"># Tu rajoutes un bouton "Suivant"</span>
<span class="n">StrictButton</span><span class="p">(</span>
<span class="s1">'<span class="glyphicon glyphicon-arrow-right" </span><span class="se">\</span>
<span class="s1"> aria-hidden="true"></span> </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="s2">"Suivant"</span><span class="p">,</span>
<span class="nb">type</span><span class="o">=</span><span class="s1">'button'</span><span class="p">,</span>
<span class="n">css_class</span><span class="o">=</span><span class="s1">'btn-default col-md-offset-9 btnNext'</span><span class="p">,</span>
<span class="p">)</span>
<span class="p">),</span>
<span class="c1"># Deuxième onglet</span>
<span class="n">Tab</span><span class="p">(</span>
<span class="c1"># Label de l'onglet</span>
<span class="s1">'Étape 2 - Adresse'</span><span class="p">,</span>
<span class="c1"># Liste des champs à afficher</span>
<span class="s1">'street_number'</span><span class="p">,</span>
<span class="s1">'street_type'</span><span class="p">,</span>
<span class="s1">'street'</span><span class="p">,</span>
<span class="s1">'comp_1'</span><span class="p">,</span>
<span class="s1">'comp_2'</span><span class="p">,</span>
<span class="s1">'city'</span><span class="p">,</span>
<span class="s1">'zip_code'</span><span class="p">,</span>
<span class="s1">'country'</span><span class="p">,</span>
<span class="s1">'phone'</span><span class="p">,</span>
<span class="c1"># Tu rajoutes des boutons "Précédent" et "Suivant"</span>
<span class="n">StrictButton</span><span class="p">(</span>
<span class="s1">'<span class="glyphicon glyphicon-arrow-left" </span><span class="se">\</span>
<span class="s1"> aria-hidden="true"></span> </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="s1">'Précédent'</span><span class="p">,</span>
<span class="nb">type</span><span class="o">=</span><span class="s1">'button'</span><span class="p">,</span>
<span class="n">css_class</span><span class="o">=</span><span class="s1">'btn-default btnPrevious'</span><span class="p">,</span>
<span class="p">),</span>
<span class="n">StrictButton</span><span class="p">(</span>
<span class="s1">'<span class="glyphicon glyphicon-arrow-right" </span><span class="se">\</span>
<span class="s1"> aria-hidden="true"></span> </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="s1">'Suivant'</span><span class="p">,</span>
<span class="nb">type</span><span class="o">=</span><span class="s1">'button'</span><span class="p">,</span>
<span class="n">css_class</span><span class="o">=</span><span class="s1">'btn-default col-md-offset-8 btnNext'</span><span class="p">,</span>
<span class="p">)</span>
<span class="p">),</span>
<span class="c1"># Troisième onglet</span>
<span class="n">Tab</span><span class="p">(</span>
<span class="c1"># Label de l'onglet</span>
<span class="s1">'Étape 3 - Validation'</span><span class="p">,</span>
<span class="c1"># Liste des champs à afficher dont les champs supplémentaires</span>
<span class="s1">'mail'</span><span class="p">,</span>
<span class="s1">'confirmation_mail'</span><span class="p">,</span>
<span class="s1">'comments'</span><span class="p">,</span>
<span class="s1">'captcha'</span><span class="p">,</span>
<span class="c1"># Tu rajoutes des boutons "Précédent" et "Valider"</span>
<span class="n">StrictButton</span><span class="p">(</span>
<span class="s1">'<span class="glyphicon glyphicon-arrow-left" </span><span class="se">\</span>
<span class="s1"> aria-hidden="true"></span> </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="s2">"Précédent"</span><span class="p">,</span>
<span class="nb">type</span><span class="o">=</span><span class="s1">'button'</span><span class="p">,</span>
<span class="n">css_class</span><span class="o">=</span><span class="s1">'btn-default btnPrevious'</span><span class="p">,</span>
<span class="p">),</span>
<span class="n">StrictButton</span><span class="p">(</span>
<span class="s1">'<span class="glyphicon glyphicon-ok" </span><span class="se">\</span>
<span class="s1"> aria-hidden="true"></span> </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="s2">"Valider"</span><span class="p">,</span>
<span class="nb">type</span><span class="o">=</span><span class="s1">'submit'</span><span class="p">,</span>
<span class="n">css_class</span><span class="o">=</span><span class="s1">'btn-default col-md-offset-8'</span>
<span class="p">)</span>
<span class="p">),</span>
<span class="p">),</span>
<span class="p">)</span>
<span class="k">def</span> <span class="nf">clean_confirmation_mail</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Méthode pour vérifier que le mail correspond bien au</span>
<span class="sd"> mail de confirmation lors de la validation du formulaire</span>
<span class="sd"> """</span>
<span class="n">confirmation_mail</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">cleaned_data</span><span class="p">[</span><span class="s1">'confirmation_mail'</span><span class="p">]</span>
<span class="n">mail</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">cleaned_data</span><span class="p">[</span><span class="s1">'mail'</span><span class="p">]</span>
<span class="k">if</span> <span class="n">mail</span> <span class="o">!=</span> <span class="n">confirmation_mail</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">forms</span><span class="o">.</span><span class="n">ValidationError</span><span class="p">(</span>
<span class="s2">"Le mail et le mail de confirmation ne sont pas identiques"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">confirmation_mail</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="c1"># Tu définis le modèle utilisé</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">Registration</span>
<span class="n">exclude</span> <span class="o">=</span> <span class="p">[]</span>
<span class="c1"># Tu customises le champ date de naissance pour ajouter le date picker</span>
<span class="n">widgets</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'birth_date'</span><span class="p">:</span> <span class="n">DateTimePicker</span><span class="p">(</span>
<span class="n">options</span><span class="o">=</span><span class="p">{</span><span class="s2">"format"</span><span class="p">:</span> <span class="s2">"DD/MM/YYYY"</span><span class="p">,</span> <span class="s2">"pickTime"</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span>
<span class="s2">"useStrict"</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span> <span class="s2">"viewMode"</span><span class="p">:</span> <span class="s2">"years"</span><span class="p">,</span>
<span class="s2">"startDate"</span><span class="p">:</span> <span class="s2">"01/01/1900"</span><span class="p">},</span>
<span class="n">attrs</span><span class="o">=</span><span class="p">{</span><span class="s1">'placeholder'</span><span class="p">:</span> <span class="s1">'ex: 05/11/1975'</span><span class="p">}</span>
<span class="p">)</span>
<span class="p">}</span>
</pre></div>
</div>
<div class="section" id="les-vues">
<h2>4) Les vues</h2>
<p>Maintenant que tu as ton formulaire, il te faut une vue pour afficher le formulaire
et une autre pour afficher un message de confirmation après la validation de celui-ci.</p>
<p>Tu vas créer tout ça dans <strong>apps/core/views.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.views.generic.edit</span> <span class="kn">import</span> <span class="n">CreateView</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Registration</span>
<span class="kn">from</span> <span class="nn">django.core.urlresolvers</span> <span class="kn">import</span> <span class="n">reverse_lazy</span>
<span class="kn">from</span> <span class="nn">django.shortcuts</span> <span class="kn">import</span> <span class="n">render</span>
<span class="kn">from</span> <span class="nn">.forms</span> <span class="kn">import</span> <span class="n">RegistrationForm</span>
<span class="k">class</span> <span class="nc">RegistrationCreate</span><span class="p">(</span><span class="n">CreateView</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Affichage du formulaire</span>
<span class="sd"> """</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">Registration</span>
<span class="n">form_class</span> <span class="o">=</span> <span class="n">RegistrationForm</span>
<span class="n">success_url</span> <span class="o">=</span> <span class="n">reverse_lazy</span><span class="p">(</span><span class="s1">'core:success'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">registration_success</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Message de confirmation</span>
<span class="sd"> """</span>
<span class="k">return</span> <span class="n">render</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="s1">'core/registration_success.html'</span><span class="p">)</span>
</pre></div>
</div>
<div class="section" id="les-urls">
<h2>5) Les urls</h2>
<p>Dans le fichier des urls du projet, tu vas inclure les urls de l'application <strong>core</strong>
et l'url pour le captcha.</p>
<p>Dans <strong>urls.py</strong>, tu insères:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.conf.urls</span> <span class="kn">import</span> <span class="n">url</span><span class="p">,</span> <span class="n">include</span>
<span class="kn">from</span> <span class="nn">django.contrib</span> <span class="kn">import</span> <span class="n">admin</span>
<span class="kn">from</span> <span class="nn">.apps.core.urls</span> <span class="kn">import</span> <span class="n">urlpatterns</span> <span class="k">as</span> <span class="n">core_urls</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^admin/'</span><span class="p">,</span> <span class="n">admin</span><span class="o">.</span><span class="n">site</span><span class="o">.</span><span class="n">urls</span><span class="p">),</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="n">core_urls</span><span class="p">,</span> <span class="n">namespace</span><span class="o">=</span><span class="s1">'core'</span><span class="p">)),</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^captcha/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="s1">'captcha.urls'</span><span class="p">)),</span>
<span class="p">]</span>
</pre></div>
<p>Et dans <strong>apps/core/urls.py</strong>, tu mets les urls correspondantes à tes deux vues:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.conf.urls</span> <span class="kn">import</span> <span class="n">patterns</span><span class="p">,</span> <span class="n">url</span>
<span class="kn">from</span> <span class="nn">.views</span> <span class="kn">import</span> <span class="n">RegistrationCreate</span><span class="p">,</span> <span class="n">registration_success</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^$'</span><span class="p">,</span> <span class="n">RegistrationCreate</span><span class="o">.</span><span class="n">as_view</span><span class="p">(),</span> <span class="n">name</span><span class="o">=</span><span class="s1">'add'</span><span class="p">),</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^success/$'</span><span class="p">,</span> <span class="n">registration_success</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'success'</span><span class="p">),</span>
<span class="p">]</span>
</pre></div>
</div>
<div class="section" id="les-templates">
<h2>6) Les templates</h2>
<p>Tu vas créer trois templates:</p>
<ul class="simple">
<li>Le premier, <strong>base.html</strong>, qui servira de base aux deux autres.</li>
<li>Le deuxième, <strong>core/registration_form.html</strong>, pour afficher le formulaire.</li>
<li>Le dernier, <strong>core/registration_success.html</strong>, pour afficher le message de confirmation.</li>
</ul>
<p>Le template <strong>apps/core/templates/base.html</strong> va contenir:</p>
<div class="highlight"><pre><span></span><span class="cp"><!DOCTYPE html></span>
<span class="p"><</span><span class="nt">html</span> <span class="na">lang</span><span class="o">=</span><span class="s">"fr"</span><span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span>Mon site<span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">"utf-8"</span> <span class="p">/></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">name</span><span class="o">=</span><span class="s">"viewport"</span> <span class="na">content</span><span class="o">=</span><span class="s">"width=device-width, initial-scale=1.0"</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">http-equiv</span><span class="o">=</span><span class="s">"X-UA-Compatible"</span> <span class="na">content</span><span class="o">=</span><span class="s">"IE=edge"</span><span class="p">></span>
<span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"stylesheet"</span>
<span class="na">href</span><span class="o">=</span><span class="s">"//cdn.jsdelivr.net/bootstrap/3.3.6/css/bootstrap.min.css"</span><span class="p">></span>
<span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">"stylesheet"</span>
<span class="na">href</span><span class="o">=</span><span class="s">"//cdn.jsdelivr.net/bootstrap/3.3.6/css/bootstrap-theme.min.css"</span><span class="p">></span>
<span class="p"><</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">"//cdn.jsdelivr.net/jquery/2.2.3/jquery.min.js"</span><span class="p">></</span><span class="nt">script</span><span class="p">></span>
{% block head-javascript %}{% endblock %}
<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"container"</span> <span class="na">role</span><span class="o">=</span><span class="s">"main"</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">id</span><span class="o">=</span><span class="s">"summary"</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"page-header"</span><span class="p">></span>
{% block page-header %}{% endblock %}
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
{% block content %}
{% endblock %}
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">"//cdn.jsdelivr.net/bootstrap/3.3.6/js/bootstrap.min.js"</span><span class="p">></</span><span class="nt">script</span><span class="p">></span>
{% block foot-javascript %}{% endblock %}
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
</pre></div>
<p>C'est une page html5 standard, qui contient les fichiers nécessaires à bootstrap,
un <strong>block page-header</strong> pour afficher le titre de la page et un <strong>block content</strong>
pour afficher le contenu de la page. Les templates suivants vont donc étendre <strong>base.html</strong>.</p>
<p>Pour le template <strong>apps/core/templates/core/registration_form.html</strong>, tu mets:</p>
<div class="highlight"><pre><span></span>{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block head-javascript %}
{{ form.media }}
{% endblock %}
{% block page-header %}
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>Inscription<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
{% endblock %}
{% block content %}
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"row"</span><span class="p">></span>{% crispy form %}<span class="p"></</span><span class="nt">div</span><span class="p">></span>
{% endblock %}
{% block foot-javascript %}
<span class="p"><</span><span class="nt">script</span><span class="p">></span>
<span class="nx">$</span><span class="p">(</span> <span class="nb">document</span> <span class="p">).</span><span class="nx">ready</span><span class="p">(</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// On interdit le copier/coller du mail</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'#id_confirmation_mail'</span><span class="p">).</span><span class="nx">bind</span><span class="p">(</span><span class="s1">'copy paste'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
<span class="p">});</span>
<span class="c1">// On affiche le calendrier lorsqu'on clique sur le champ date de naissance</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'#id_birth_date'</span><span class="p">).</span><span class="nx">click</span><span class="p">(</span><span class="kd">function</span><span class="p">(){</span>
<span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nx">parent</span><span class="p">().</span><span class="nx">data</span><span class="p">(</span><span class="s2">"DateTimePicker"</span><span class="p">).</span><span class="nx">show</span><span class="p">();</span>
<span class="p">});</span>
<span class="c1">// On teste la valeur de la date et on la force à vide si elle est mauvaise</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'#id_birth_date'</span><span class="p">).</span><span class="nx">change</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">){</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">stopPropagation</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">val</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nx">val</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">format</span> <span class="o">=</span> <span class="ow">new</span> <span class="nb">RegExp</span><span class="p">(</span><span class="s2">"^\\d{2}\/\\d{2}\/\\d{4}$"</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">format</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">val</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">).</span><span class="nx">val</span><span class="p">(</span><span class="s2">""</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="c1">// Afficher l'onglet suivant en cliquant sur le bouton suivant</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'.btnNext'</span><span class="p">).</span><span class="nx">click</span><span class="p">(</span><span class="kd">function</span><span class="p">(){</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'.nav-tabs > .active + li a'</span><span class="p">).</span><span class="nx">trigger</span><span class="p">(</span><span class="s1">'click'</span><span class="p">);</span>
<span class="nx">$</span><span class="p">(</span><span class="s2">".nav-tabs + .tab-content"</span><span class="p">).</span><span class="nx">find</span><span class="p">(</span><span class="s2">":input:visible:first"</span><span class="p">).</span><span class="nx">focus</span><span class="p">();</span>
<span class="p">});</span>
<span class="c1">// Afficher l'onglet précédent en cliquant sur le bouton précédent</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'.btnPrevious'</span><span class="p">).</span><span class="nx">click</span><span class="p">(</span><span class="kd">function</span><span class="p">(){</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'.nav-tabs > .active'</span><span class="p">).</span><span class="nx">prev</span><span class="p">(</span><span class="s1">'li'</span><span class="p">).</span><span class="nx">find</span><span class="p">(</span><span class="s1">'a'</span><span class="p">).</span><span class="nx">trigger</span><span class="p">(</span><span class="s1">'click'</span><span class="p">);</span>
<span class="nx">$</span><span class="p">(</span><span class="s2">".nav-tabs + .tab-content"</span><span class="p">).</span><span class="nx">find</span><span class="p">(</span><span class="s2">":input:visible:first"</span><span class="p">).</span><span class="nx">focus</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p"></</span><span class="nt">script</span><span class="p">></span>
{% endblock %}
</pre></div>
<p>Pour <em>crispy-forms</em>, il ne faut que deux éléments ! Le <strong>{% load crispy_forms_tags %}</strong>,
qui permet d'utiliser le <strong>{% crispy form %}</strong> pour afficher le formulaire.</p>
<p>Et c'est tout ! Plutôt cool non ?</p>
<p>Le <strong>{{ form.media }}</strong> est nécessaire au date picker et tout ce qui se trouve dans
le <strong>block foot-javascript</strong> permet de pousser un peu plus la customisation.</p>
<p>Enfin, de la même manière, tu crées le template <strong>apps/core/templates/core/registration_success.html</strong>:</p>
<div class="highlight"><pre><span></span>{% extends "base.html" %}
{% block page-header %}
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>Succès<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
{% endblock %}
{% block content %}
<span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="o">=</span><span class="s">"row"</span><span class="p">></span>Votre inscription a été validée.<span class="p"></</span><span class="nt">div</span><span class="p">></span>
{% endblock %}
</pre></div>
</div>
<div class="section" id="la-page-d-administration">
<h2>7) La page d'administration</h2>
<p>Pour pouvoir vérifier les futures inscriptions, tu ajoutes le modèle <strong>Registration</strong> dans <strong>apps/core/admin.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.contrib</span> <span class="kn">import</span> <span class="n">admin</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Registration</span>
<span class="k">class</span> <span class="nc">RegistrationAdmin</span><span class="p">(</span><span class="n">admin</span><span class="o">.</span><span class="n">ModelAdmin</span><span class="p">):</span>
<span class="n">list_display</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'birth_name'</span><span class="p">,</span> <span class="s1">'last_name'</span><span class="p">,</span> <span class="s1">'first_name'</span><span class="p">)</span>
<span class="n">admin</span><span class="o">.</span><span class="n">site</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">Registration</span><span class="p">,</span> <span class="n">RegistrationAdmin</span><span class="p">)</span>
</pre></div>
<p>Tu vas également en profiter pour ajouter un compte admin via:</p>
<div class="highlight"><pre><span></span>./manage.py createsuperuser
</pre></div>
</div>
<div class="section" id="la-verification">
<h2>8) La vérification</h2>
<p>Tout est fini ! Il ne reste plus qu'à vérifier que ça fonctionne correctement.</p>
<p>Tu démarres le serveur:</p>
<div class="highlight"><pre><span></span>./manage.py runserver
</pre></div>
<p>Puis, tu te rends sur <a class="reference external" href="http://127.0.0.1:8000">http://127.0.0.1:8000</a>
pour tester ton formulaire !</p>
<img alt="Crispy" class="align-left" src="./images/crispy1.png" />
<p>Après avoir correctement saisie et validé le formulaire, rends-toi sur
<a class="reference external" href="http://127.0.0.1:8000/admin/core/registration/">http://127.0.0.1:8000/admin/core/registration/</a>
pour vérifier ton inscription.</p>
<p>Tu sais désormais utiliser un outil puissant pour générer tes nombreux formulaires !
Et pour voir le résultat final, tu peux te rendre sur
<a class="reference external" href="https://github.com/dotmobo/demo-django-crispy-forms">mon dépôt github</a>.</p>
</div>
Environnement de développement open source2016-04-19T00:00:00+02:002016-04-19T00:00:00+02:00Morgantag:dotmobo.github.io,2016-04-19:/environnement-developpement.html<p class="first last">Environnement de développement open source</p>
<div class="line-block">
<div class="line">Envie de faire évoluer ton environnement de développement ?</div>
<div class="line">D'utiliser une solution simple, pratique et efficace ?</div>
<div class="line">Alors, tu es au bon endroit !</div>
<div class="line"><br /></div>
</div>
<p>On va faire le tour des outils indispensables pour mettre ça en place.
Bien évidemment, mon avis est subjectif et ce n'est qu'une solution parmis tant
d'autres.</p>
<p>C'est parti !</p>
<div class="section" id="la-distro">
<h2>1) La distro</h2>
<img alt="arch linux" class="align-right" src="./images/arch.png" />
<p>On commence par le choix de la distribution. Je te conseille vivement de partir
sur <a class="reference external" href="https://www.archlinux.org/">arch linux</a> pour les raisons suivantes:</p>
<ul class="simple">
<li><a class="reference external" href="https://fr.wikipedia.org/wiki/Rolling_release">rolling-release</a>:
ton système est tout le temps à jour et tu bénéficies des dernières
versions des logiciels.</li>
<li>légèreté: l'installation de base est minimaliste, ce qui fait que tu n'installes
que ce dont tu as besoin.</li>
<li>la communauté: le <a class="reference external" href="https://wiki.archlinux.org/">wiki</a> et le
<a class="reference external" href="https://bbs.archlinux.org/">forum</a> de la communauté sont vraiment très bien
foutus. Il y a même une <a class="reference external" href="https://archlinux.fr/">communauté francophone</a>.</li>
<li><a class="reference external" href="https://aur.archlinux.org/">AUR</a>: c'est un dépôt communautaire où l'on trouve
absolument tout.</li>
</ul>
<p>Les principales critiques envers arch linux concernent en général la stabilité du système.
La stabilité, c'est bien pour un serveur. Mais ce n'est pas forcément le plus important
pour le poste de travail d'un dev. Avec arch, tu pourras tester les toutes
dernières versions des logiciels et utiliser les nouvelles librairies à la mode !</p>
<p>Pour l'installer, je t'invites à le faire à la main en suivant
la <a class="reference external" href="https://wiki.archlinux.fr/Installation">doc officielle</a>, histoire d'entrer dans le bain.
Si tu as vraiment la flemme, tu peux passer par
<a class="reference external" href="https://sourceforge.net/projects/architect-linux/">architect</a> pour te faciliter la chose.</p>
<p>N'oublie pas d'installer <a class="reference external" href="https://wiki.archlinux.fr/Yaourt">yaourt</a> pour bénifier du dépôt AUR.</p>
<p>Tu auras remarqué que les tutos de ce blog sont en général à destination
des utilisateurs ubuntu/debian. Pas de panique, il te suffira grosso-modo de
remplacer les <strong>apt-get install</strong> par <strong>pacman -S</strong> pour installer les paquets.
Pour le reste, c'est généralement du pareil au même.</p>
</div>
<div class="section" id="le-shell">
<h2>2) Le shell</h2>
<img alt="zsh" class="align-right" src="./images/zsh.gif" />
<p>Maintenant, tu vas t'installer un shell très pratique appelé <a class="reference external" href="https://wiki.archlinux.fr/Zsh">zsh</a>,
qui permet une complétion avancée et l'utilisation d'une syntaxe plus sympa que bash pour
les scripts.</p>
<p>Et en plus de zsh, tu vas également t'installer <a class="reference external" href="https://tmux.github.io/">tmux</a>,
qui est un multiplexeur de terminaux.
Il permet de manipuler plusieurs terminaux au sein d'une même fenêtre et
de se rattacher à une session tmux distante. C'est très pratique pour le
travail collaboratif et pour lancer des commandes sur des serveurs.</p>
<div class="highlight"><pre><span></span>pacman -S zsh tmux
<span class="nb">exec</span> zsh
chsh -s /bin/zsh
</pre></div>
<p>Il ne te reste plus qu'à <a class="reference external" href="https://github.com/dotmobo/dotzsh">customiser ton .zshrc</a>
comme bon de semble.</p>
</div>
<div class="section" id="le-gestionnaire-de-fenetre">
<h2>3) Le gestionnaire de fenêtre</h2>
<img alt="i3" class="align-right" src="./images/i3.png" />
<p>Oublie les kde, gnome et autres cinammon. Ces environnements sont très bien
pour un usage classique de linux ; mais pour du dev, il vaut mieux se tourner vers
un <a class="reference external" href="https://en.wikipedia.org/wiki/Tiling_window_manager">tiling window manager</a>.</p>
<p>Je te conseille de partir sur <a class="reference external" href="https://i3wm.org/">i3</a>, qui est facile d'accès,
customisable et efficace. Il te permettra d'utiliser l'ensemble de l'espace visible
disponible, de splitter tes fenêtres comme bon te semble et d'exécuter tes applications.
De plus, tu pourras manipuler tout ton bureau
<a class="reference external" href="http://i3wm.org/docs/userguide.html#_default_keybindings">au clavier</a>,
ce qui fait un sacré gain de temps.</p>
<div class="highlight"><pre><span></span>pacman -S i3
<span class="nb">echo</span> <span class="s2">"exec i3"</span> > ~/.xinitrc
reboot
</pre></div>
<p>A côté d'i3, tu pourras installer entre autres:</p>
<ul class="simple">
<li><a class="reference external" href="https://www.npmjs.com/package/i3-style">i3-style</a>: pour styliser i3.</li>
<li><a class="reference external" href="https://wiki.archlinux.org/index.php/Dmenu">dmenu</a>: pour avoir un lanceur d'applications.</li>
<li><a class="reference external" href="https://wiki.archlinux.org/index.php/i3#i3bar">i3bar et i3status</a>:
pour avoir une barre de status en bas de l'écran.</li>
<li><a class="reference external" href="https://wiki.archlinux.org/index.php/i3#Screensaver_and_power_management">i3lock et xautolock</a>:
pour verrouiller l'écran au bout de quelques minutes d'inactivité.</li>
<li><a class="reference external" href="http://projects.l3ib.org/nitrogen/">nitrogen</a>: pour changer l'image de fond.</li>
<li>pactl de <a class="reference external" href="https://wiki.archlinux.org/index.php/PulseAudio">pulseaudio</a>:
pour changer le volume au clavier.</li>
<li><a class="reference external" href="http://knopwob.org/dunst/index.html">dunst</a>: pour les notifications.</li>
<li><a class="reference external" href="http://jonls.dk/redshift/">redshift</a>: pour éviter de se fatiguer les yeux.</li>
<li><a class="reference external" href="http://wiki.lxde.org/en/PCManFM">pcmanfm</a>: pour la gestion des fichiers.</li>
<li><a class="reference external" href="https://www.mozilla.org/fr/firefox/">firefox</a>: pour la navigation web.</li>
</ul>
<p>Jete un coup d'oeil à <a class="reference external" href="https://github.com/dotmobo/doti3">ma customisation</a>
si ça t'intéresse.</p>
<p>Et si tu as besoin d'aide, la <a class="reference external" href="https://www.reddit.com/r/i3wm/">communauté reddit</a>
est très active.</p>
</div>
<div class="section" id="je-veux-coder">
<h2>4) Je veux coder !</h2>
<img alt="vim" class="align-right" src="./images/vim.png" />
<p>Pour coder, tu auras besoin d'un éditeur de texte.
Je te recommande fortement <a class="reference external" href="https://atom.io/">atom</a>, que j'ai déjà présenté
<a class="reference external" href="http://dotmobo.github.io/sublime-text-to-atom.html">ici</a>.</p>
<p>En plus d'atom, tu auras besoin d'un éditeur qui peut s'exécuter dans un terminal.
Pas besoin de chercher bien loin, le plus avancé est <a class="reference external" href="http://www.vim.org/">vim</a>.
Mais tu peux également te tourner vers son successeur, <a class="reference external" href="https://neovim.io/">neovim</a>.</p>
<p>Comme le reste des outils présentés sur cet article, il est aussi <a class="reference external" href="https://github.com/dotmobo/dotvim">très fortement
customisable</a>.</p>
<p>En plus des éditeurs de texte, il te faudra un gestionnaire de version de code.
Sans surprise, je t'invite à installer <a class="reference external" href="https://git-scm.com/">git</a>,
qui te permettra de partager ton code sous github et qui s'interface très bien
avec atom.</p>
<div class="highlight"><pre><span></span>pacman -S git vim
yaourt -S atom
</pre></div>
<p>Enfin, à toi d'installer ce qui te manque:
python, node.js, postgresql, nginx, etc ...</p>
</div>
<div class="section" id="environnements-isoles">
<h2>5) Environnements isolés</h2>
<img alt="docker" class="align-right" src="./images/docker.png" />
<p>Dans le cas où tu aurais besoin de tester des applications sous d'autres
distributions, tu pourras utiliser <a class="reference external" href="https://www.vagrantup.com/">vagrant</a>
pour installer des vms ou <a class="reference external" href="https://www.docker.com/">docker</a> pour utiliser des conteneurs.</p>
<div class="highlight"><pre><span></span>yaourt -S vagrant docker docker-compose
</pre></div>
<p>A l'aide de <a class="reference external" href="https://docs.docker.com/compose/">docker-compose</a>,
tu pourras te créer un environnement spécifique par application en utilisant des conteneurs.
Par exemple, un pour elasticsearch, un autre pour mysql,
un troisième pour redis et un quatrième pour ton application django.</p>
<p>Tu bénificieras ainsi d'environnements complètement isolés, sans devoir installer des tonnes
d'applications directement sur ton système.</p>
<p>A toi de jouer maintenant, et n'hésite pas à donner tes propres astuces dans les commentaires !</p>
</div>
Servir une application web python avec chaussette, circus et nginx2016-04-09T00:00:00+02:002016-04-09T00:00:00+02:00Morgantag:dotmobo.github.io,2016-04-09:/chaussette-circus.html<p class="first last">Servir une application web python avec chaussette, circus et nginx</p>
<img alt="Chaussette" class="align-right" src="./images/chaussette.png" />
<p>Voilà, tu as enfin développé ton application django/bottle/flask (raye les
mentions inutiles) et tu es fin prêt à la rendre disponible au monde entier.</p>
<p><em>Mais comment faire ? Quel serveur wsgi utiliser ? A l'aide !</em></p>
<p>Il existe différentes alternatives, mais je vais te proposer celle que j'affectionne
tout particulièrement, à savoir le trio chaussette/circus/nginx.</p>
<div class="section" id="creer-une-application-web">
<h2>1) Créer une application web</h2>
<p>Tu vas commencer par créer une application <a class="reference external" href="http://bottlepy.org/">bottle</a>
de base (pour changer de django, tiens !).</p>
<p>Tu crées et actives un environnement virtuel <strong>myapp</strong> en python 3.5 à l'aide de
<em>virtualenvwrapper</em>:</p>
<div class="highlight"><pre><span></span><span class="n">mkdir</span> <span class="n">myproject</span> <span class="o">&&</span> <span class="n">cd</span> <span class="n">myproject</span>
<span class="n">mkvirtualenv</span> <span class="o">-</span><span class="n">p</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="nb">bin</span><span class="o">/</span><span class="n">python3</span><span class="mf">.5</span> <span class="n">myapp</span>
</pre></div>
<p>Tu installes bottle dans ton virtualenv:</p>
<div class="highlight"><pre><span></span>pip install bottle
</pre></div>
<p>Dans le répertoire de ton projet, tu crées un module python <strong>myapp</strong> qui contient
un fichier vide <strong>__init__.py</strong> et un fichier <strong>wsgi.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="n">mkdir</span> <span class="n">myapp</span> <span class="o">&&</span> <span class="n">touch</span> <span class="n">myapp</span><span class="o">/</span><span class="fm">__init__</span><span class="o">.</span><span class="n">py</span> <span class="n">myapp</span><span class="o">/</span><span class="n">wsgi</span><span class="o">.</span><span class="n">py</span>
</pre></div>
<p>Ton fichier <strong>wsgi.py</strong> doit ressembler à ça:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">bottle</span> <span class="kn">import</span> <span class="n">route</span><span class="p">,</span> <span class="n">template</span><span class="p">,</span> <span class="n">default_app</span>
<span class="nd">@route</span><span class="p">(</span><span class="s1">'/hello/<name>'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">index</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
<span class="k">return</span> <span class="n">template</span><span class="p">(</span><span class="s1">'<b>Hello {{name}}</b>!'</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">)</span>
<span class="n">application</span> <span class="o">=</span> <span class="n">default_app</span><span class="p">()</span>
</pre></div>
<p>Pour information, sous django, le fichier à utiliser s'appelle aussi <strong>wsgi.py</strong>.</p>
</div>
<div class="section" id="installer-chaussette-et-waitress">
<h2>2) Installer chaussette et waitress</h2>
<p>Maintenant, tu vas utiliser <a class="reference external" href="https://chaussette.readthedocs.org">chaussette</a>
pour lancer ton application web.</p>
<p>Chaussette est <a class="reference external" href="http://sametmax.com/quest-ce-que-wsgi-et-a-quoi-ca-sert/">un serveur wsgi</a>
qui permet le dialogue entre les requêtes http et ton application python.
Il autorise l'utilisation de différents backends wsgi, comme
<a class="reference external" href="http://waitress.readthedocs.org/">waitress</a> par exemple,
et a également la particularité de pouvoir servir ton application sur un socket déjà
ouvert. Dis comme ça, ça n'a l'air de rien, mais ça permet du coup d'utiliser un
manager de sockets comme circus ou supervisor.</p>
<p>Tu installes chaussette et waitress en étant <strong>dans</strong> le virtualenv <strong>myapp</strong>:</p>
<div class="highlight"><pre><span></span>pip install chaussette waitress
</pre></div>
<p>Et tu lances ton serveur wsgi depuis le répertoire de ton projet <strong>myproject</strong>:</p>
<div class="highlight"><pre><span></span>chaussette --backend waitress myapp.wsgi.application
</pre></div>
<p>Rends-toi sur <a class="reference external" href="http://127.0.0.1:8080/hello/you">http://127.0.0.1:8080/hello/you</a>,
tu devrais voir ta page apparaître. Si tout est ok, tu peux couper chaussette et
passer à la suite.</p>
</div>
<div class="section" id="configurer-circus">
<h2>3) Configurer circus</h2>
<p>Place au spectacle !</p>
<p><a class="reference external" href="http://circus.readthedocs.org/">Circus</a> est, grosso-modo, un manager de processus
et de sockets compatible python 2 et python 3. C'est lui qui va se charger de
monitorer et de redémarrer tes applications.
Il permet d'utiliser les virtualenvs et se marie très bien
avec chaussette. Ce projet nous vient à l'origine de la
<a class="reference external" href="https://www.mozilla.org/en-US/foundation/">fondation mozilla</a>.</p>
<p>Tu l'installes via pip <strong>en dehors</strong> du virtualenv <strong>myapp</strong> (donc en global sur ton système):</p>
<div class="highlight"><pre><span></span>deactivate
apt-get install libzmq-dev libevent-dev
pip install circus
</pre></div>
<p>Et tu crées le fichier de configuration de circus <strong>circus.ini</strong> dans ton <em>home</em>
par exemple. C'est là que tu vas pouvoir configurer tes <em>watchers</em>, qui vont
lancer tes processus chaussette.</p>
<p>Dans le fichier <strong>~/circus.ini</strong>, tu mets:</p>
<div class="highlight"><pre><span></span><span class="o">[</span>circus<span class="o">]</span>
<span class="nv">statsd</span> <span class="o">=</span> <span class="m">1</span>
<span class="nv">httpd</span> <span class="o">=</span> <span class="m">0</span>
<span class="o">[</span>watcher:myapp<span class="o">]</span>
<span class="nv">cmd</span> <span class="o">=</span> /home/TONUSER/.virtualenvs/myapp/bin/chaussette --fd <span class="k">$(</span>circus.sockets.web<span class="k">)</span> --backend waitress myapp.wsgi.application
<span class="nv">working_dir</span> <span class="o">=</span> /home/TONUSER/LECHEMINVERSTONPROJET/myproject
<span class="nv">numprocesses</span> <span class="o">=</span> <span class="m">3</span>
<span class="nv">copy_env</span> <span class="o">=</span> <span class="m">1</span>
<span class="nv">use_sockets</span> <span class="o">=</span> <span class="m">1</span>
<span class="nv">virtualenv</span> <span class="o">=</span> /home/TONUSER/.virtualenvs/myapp
<span class="nv">virtualenv_py_ver</span> <span class="o">=</span> <span class="m">3</span>.5
<span class="o">[</span>socket:web<span class="o">]</span>
<span class="nv">host</span> <span class="o">=</span> <span class="m">127</span>.0.0.1
<span class="nv">port</span> <span class="o">=</span> <span class="m">8001</span>
</pre></div>
<p>N'oublie pas de modifier les différents chemins de <strong>working_dir</strong>, <strong>cmd</strong> et
<strong>virtualenv</strong> pour que ça correspondent à ta propre machine. Tu peux également
configurer plusieurs <em>watchers</em> si tu souhaites monitorer plusieurs applications
web.</p>
<p>Enfin, tu lances le <em>daemon</em> de circus:</p>
<div class="highlight"><pre><span></span>circusd --daemon ~/circus.ini
</pre></div>
<p>Si tout s'est bien passé, tu devrais pouvoir utiliser la commande <strong>circusctl</strong>
pour voir le statut de tes applications, les redémarrer et autres.
Sinon, tu peux exécuter <strong>circusd</strong> sans l'option <strong>--daemon</strong> pour debugger.</p>
<p>Tu peux voir ci-dessous quelques exemples d'utilisation de <strong>circusctl</strong>:</p>
<div class="highlight"><pre><span></span>circusctl --help <span class="c1"># voir l'ensemble des commandes disponibles</span>
circusctl status <span class="c1"># voir le statut des applications</span>
circusctl listsockets <span class="c1"># lister les sockets utilisés par les applications</span>
circusctl restart myapp <span class="c1"># redémarrer myapp</span>
circusctl reload myapp <span class="c1"># recharcher la configuration du watcher myapp</span>
</pre></div>
<p>Rends-toi sur <a class="reference external" href="http://127.0.0.1:8001/hello/you">http://127.0.0.1:8001/hello/you</a>
pour vérifier que tout fonctionne.</p>
<p>Grâce à circus, tu peux désormais manager plusieurs applications différentes,
qui tournent sous des environnements virtuels différents.</p>
<p>Pour information, il existe une interface web pour monitorer circus appelé
<strong>circus-web</strong>, mais qui n'est pas encore compatible python 3.</p>
</div>
<div class="section" id="parametrer-nginx">
<h2>3) Paramétrer nginx</h2>
<p>Bon, il ne te reste plus qu'à mettre en place nginx. C'est un
serveur http libre et performant qui est une très bonne alternative à apache.
Il va nous permettre de transmettre les requêtes http à circus/chaussette via les
sockets.</p>
<p>Tu l'installes via <em>apt-get</em> par exemple:</p>
<div class="highlight"><pre><span></span>apt-get install nginx
</pre></div>
<p>Et tu vas créer la configuration suivante dans <strong>/etc/nginx/sites-available/myapp.conf</strong>:</p>
<div class="highlight"><pre><span></span>upstream myapp <span class="o">{</span>
server <span class="m">127</span>.0.0.1:8001<span class="p">;</span>
<span class="o">}</span>
server <span class="o">{</span>
listen <span class="m">80</span><span class="p">;</span>
server_name localhost<span class="p">;</span>
location / <span class="o">{</span>
proxy_pass http://myapp<span class="nv">$request_uri</span><span class="p">;</span>
proxy_redirect off<span class="p">;</span>
proxy_set_header Host <span class="nv">$host</span><span class="p">;</span>
proxy_set_header X-Real-IP <span class="nv">$remote_addr</span><span class="p">;</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<p>Tu actives ta conf', tu supprimes le site par défaut et tu redémarres nginx:</p>
<div class="highlight"><pre><span></span>ln -s /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/myapp
rm /etc/nginx/sites-enabled/default
service nginx restart <span class="c1">#ou via systemd selon ta distro</span>
</pre></div>
<p>Il ne te reste plus qu'à te rendre sur <a class="reference external" href="http://localhost/hello/you">http://localhost/hello/you</a> pour observer le résultat !</p>
</div>
Créer une application django minimaliste2016-04-04T00:00:00+02:002016-04-04T00:00:00+02:00Morgantag:dotmobo.github.io,2016-04-04:/django-minimaliste.html<p class="first last">Créer une application django minimaliste</p>
<img alt="Django" class="align-right" src="./images/djangopony.png" />
<p><a class="reference external" href="https://www.djangoproject.com/">Django</a> est un framework très complet,
qui nécessite un certain investissement pour entrer dans le bain.</p>
<p>Il est donc le plus souvent utilisé pour créer des applications conséquentes,
ce qui amène les développeurs à préférer des frameworks légers comme
<a class="reference external" href="http://bottlepy.org/">Bottle</a> ou <a class="reference external" href="http://flask.pocoo.org/">Flask</a> pour le
développement de petites applications.</p>
<p>Pourtant, il est possible d'utiliser Django de manière minimaliste, afin
d'obtenir un résultat proche des frameworks légers.</p>
<p>L'astuce décrite ci-dessous est à garder quelque part dans un coin de ta tête,
car ça pourra sûrement t'être utile un jour.</p>
<p>Après avoir installé django (1.9.5 à l'heure où j'écris ces lignes):</p>
<div class="highlight"><pre><span></span>pip install django
</pre></div>
<p>Tu crées le fichier <strong>myserver.py</strong> suivant dans le répertoire de ton pojet:</p>
<div class="highlight"><pre><span></span><span class="c1"># -*- coding: utf-8 -*-</span>
<span class="sd">"""</span>
<span class="sd"> Django minimaliste</span>
<span class="sd">"""</span>
<span class="kn">from</span> <span class="nn">django.conf</span> <span class="kn">import</span> <span class="n">settings</span>
<span class="kn">from</span> <span class="nn">django.conf.urls</span> <span class="kn">import</span> <span class="n">url</span>
<span class="kn">from</span> <span class="nn">django.shortcuts</span> <span class="kn">import</span> <span class="n">render</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="c1"># On définit les settings obligatoires et utiles</span>
<span class="n">settings</span><span class="o">.</span><span class="n">configure</span><span class="p">(</span>
<span class="n">DEBUG</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
<span class="n">SECRET_KEY</span><span class="o">=</span><span class="s1">'S3CR3T'</span><span class="p">,</span>
<span class="n">ROOT_URLCONF</span><span class="o">=</span><span class="vm">__name__</span><span class="p">,</span>
<span class="n">TEMPLATES</span><span class="o">=</span><span class="p">[{</span>
<span class="s1">'BACKEND'</span><span class="p">:</span> <span class="s1">'django.template.backends.django.DjangoTemplates'</span><span class="p">,</span>
<span class="s1">'DIRS'</span><span class="p">:</span> <span class="p">[</span><span class="s1">'./templates/'</span><span class="p">],</span>
<span class="p">}],</span>
<span class="n">MIDDLEWARE_CLASSES</span><span class="o">=</span><span class="p">(</span>
<span class="s1">'django.middleware.common.CommonMiddleware'</span><span class="p">,</span>
<span class="s1">'django.middleware.csrf.CsrfViewMiddleware'</span><span class="p">,</span>
<span class="s1">'django.middleware.clickjacking.XFrameOptionsMiddleware'</span><span class="p">,</span>
<span class="p">),</span>
<span class="p">)</span>
<span class="c1"># On crée nos vues</span>
<span class="k">def</span> <span class="nf">index</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">someone</span><span class="o">=</span><span class="s2">"World"</span><span class="p">):</span>
<span class="k">return</span> <span class="n">render</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="s1">'index.html'</span><span class="p">,</span> <span class="p">{</span><span class="s2">"someone"</span><span class="p">:</span> <span class="n">someone</span><span class="p">})</span>
<span class="c1"># On délare nos urls</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">(</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^$'</span><span class="p">,</span> <span class="n">index</span><span class="p">),</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^(?P<someone>\w+)/$'</span><span class="p">,</span> <span class="n">index</span><span class="p">),</span>
<span class="p">)</span>
<span class="c1"># On exécute le serveur comme s'il s'agissait d'un fichier manage.py</span>
<span class="c1"># via python myserver.py runserver</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
<span class="kn">from</span> <span class="nn">django.core.management</span> <span class="kn">import</span> <span class="n">execute_from_command_line</span>
<span class="n">execute_from_command_line</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span>
</pre></div>
<p>Tu as donc écrit:</p>
<ul class="simple">
<li>Les paramètres de configuration de base, incluant le nom du répertoire qui
contiendra les templates.</li>
<li>Une vue <strong>index</strong> qui appelle le template <strong>index.html</strong>.</li>
<li>Les patterns de tes urls.</li>
<li>Le <strong>main</strong> qui va te permettre d'exécuter l'ensemble des commandes django.</li>
</ul>
<p>Evidemment, tu peux faire évoluer ce fichier selon tes besoins, comme pour
rajouter une base de données par exemple.</p>
<p>Maintenant, tu vas créer le template <strong>index.html</strong> suivant dans un sous-répertoire
appelé <strong>templates</strong>:</p>
<div class="highlight"><pre><span></span><span class="cp"><!DOCTYPE html></span>
<span class="p"><</span><span class="nt">html</span><span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">"utf-8"</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span>Hello World!<span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span><span class="p">></span>Hello {{ someone }} !<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
</pre></div>
<p>Il ne te reste plus qu'à lancer le serveur via la commande:</p>
<div class="highlight"><pre><span></span>python myserver.py runserver
</pre></div>
<p>Enfin, rends-toi sur <a class="reference external" href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a> et <a class="reference external" href="http://127.0.0.1:8000/arthur/">http://127.0.0.1:8000/arthur/</a>
pour voir le résultat !</p>
Programmation fonctionnelle avec PyToolz2016-03-03T00:00:00+01:002016-03-03T00:00:00+01:00Morgantag:dotmobo.github.io,2016-03-03:/pytoolz.html<p class="first last">Programmation fonctionnelle avec PyToolz</p>
<img alt="Python" class="align-right" src="./images/python.png" />
<p><a class="reference external" href="http://toolz.readthedocs.org/en/latest/index.html">PyToolz</a> est un ensemble
de fonctions qui étendent <em>itertools</em> et <em>functools</em> de la librairie
standard. Ainsi, il va te permettre d'utiliser les
paradigmes de la programmation fonctionnelle en python.</p>
<p>Ce qui est sympa avec <em>PyToolz</em>, c'est qu'il est écrit en pur python et ne
dépend donc d'aucune librairie externe. De plus, il est compatible python 3.</p>
<p>Comme le veut la programmation fonctionnelle, les fonctions proposées sont
<em>composable</em>, <em>pure</em> et <em>lazy</em>. Si tout ça ne te parle pas, je t'invite à lire
le livre "<a class="reference external" href="http://lyah.haskell.fr/">Apprendre Haskell vous fera le plus grand bien !</a>"
pour te plonger dans l'univers de la programmation fonctionnelle.
Même si tu ne comptes pas développer en <a class="reference external" href="https://www.haskell.org/">Haskell</a>,
il est intéressant d'en comprendre les concepts.</p>
<p>Mais retournons à <em>PyToolz</em>. Comme d'hab, tu l'installes avec pip:</p>
<div class="highlight"><pre><span></span>pip install toolz
</pre></div>
<p>Et tu peux maintenant utiliser toutes les méthodes de
<a class="reference external" href="http://toolz.readthedocs.org/en/latest/api.html#itertoolz">Itertoolz</a>,
<a class="reference external" href="http://toolz.readthedocs.org/en/latest/api.html#functoolz">Functoolz</a> et
<a class="reference external" href="http://toolz.readthedocs.org/en/latest/api.html#dicttoolz">Dictoolz</a>.</p>
<p><em>Itertoolz</em> te permettra, par exemple, de retirer les éléments pairs d'une liste:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">toolz.itertoolz</span> <span class="kn">import</span> <span class="n">remove</span>
<span class="o">>>></span> <span class="nb">list</span><span class="p">(</span><span class="n">remove</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]))</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">]</span>
</pre></div>
<p>De récupérer les deux premiers éléments d'une liste:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">toolz.itertoolz</span> <span class="kn">import</span> <span class="n">take</span>
<span class="o">>>></span> <span class="nb">list</span><span class="p">(</span><span class="n">take</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]))</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span>
</pre></div>
<p>De partitionner une liste d'éléments en liste de tuples de deux éléments:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">toolz.itertoolz</span> <span class="kn">import</span> <span class="n">partition</span>
<span class="o">>>></span> <span class="nb">list</span><span class="p">(</span><span class="n">partition</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]))</span>
<span class="p">[(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">)]</span>
</pre></div>
<p>Ou encore de retourner les différences entre deux listes:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">toolz.itertoolz</span> <span class="kn">import</span> <span class="n">diff</span>
<span class="o">>>></span> <span class="nb">list</span><span class="p">(</span><span class="n">diff</span><span class="p">([</span><span class="s2">"pommes"</span><span class="p">,</span> <span class="s2">"poires"</span><span class="p">,</span> <span class="s2">"bananes"</span><span class="p">],</span> <span class="p">[</span><span class="s2">"pommes"</span><span class="p">,</span> <span class="s2">"poires"</span><span class="p">,</span> <span class="s2">"oranges"</span><span class="p">]))</span>
<span class="p">[(</span><span class="s1">'bananes'</span><span class="p">,</span> <span class="s1">'oranges'</span><span class="p">)]</span>
</pre></div>
<p><em>PyToolz</em> propose également la version <em>curryfiée</em> des différentes
fonctions pour en effectuer des applications partielles. Ici, on crée les
fonctions <strong>remove_even</strong> et <strong>take_two</strong>:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">toolz.curried</span> <span class="kn">import</span> <span class="n">remove</span><span class="p">,</span> <span class="n">take</span>
<span class="o">>>></span> <span class="n">remove_even</span> <span class="o">=</span> <span class="n">remove</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="o">>>></span> <span class="nb">list</span><span class="p">(</span><span class="n">remove_even</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]))</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">]</span>
<span class="o">>>></span> <span class="n">take_two</span> <span class="o">=</span> <span class="n">take</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="o">>>></span> <span class="nb">list</span><span class="p">(</span><span class="n">take_two</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">]))</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span>
</pre></div>
<p>Et maintenant, place au fun avec <strong>functoolz</strong> ! Comme en <em>shell</em>, tu vas
pouvoir <em>piper</em> tes fonctions. Disons que tu veuilles les deux premiers
éléments non paires d'une liste:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">toolz.curried</span> <span class="kn">import</span> <span class="n">remove</span><span class="p">,</span> <span class="n">take</span>
<span class="o">>>></span> <span class="kn">from</span> <span class="nn">toolz.functoolz</span> <span class="kn">import</span> <span class="n">pipe</span>
<span class="o">>>></span> <span class="n">remove_even</span> <span class="o">=</span> <span class="n">remove</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">take_two</span> <span class="o">=</span> <span class="n">take</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="o">>>></span> <span class="nb">list</span><span class="p">(</span><span class="n">pipe</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">],</span> <span class="n">remove_even</span><span class="p">,</span> <span class="n">take_two</span><span class="p">))</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</pre></div>
<p>Plutôt cool non ?</p>
<p><strong>Dictoolz</strong>, quand à lui, permet entre autres de fusionner des dictionnaires:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">toolz.dicttoolz</span> <span class="kn">import</span> <span class="n">merge</span>
<span class="o">>>></span> <span class="n">merge</span><span class="p">({</span><span class="mi">1</span><span class="p">:</span> <span class="s1">'one'</span><span class="p">},</span> <span class="p">{</span><span class="mi">2</span><span class="p">:</span> <span class="s1">'two'</span><span class="p">})</span>
<span class="p">{</span><span class="mi">1</span><span class="p">:</span> <span class="s1">'one'</span><span class="p">,</span> <span class="mi">2</span><span class="p">:</span> <span class="s1">'two'</span><span class="p">}</span>
</pre></div>
<p>Ou d'appliquer une fonction aux valeurs d'un dictionnaire:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">toolz.dicttoolz</span> <span class="kn">import</span> <span class="n">valmap</span>
<span class="o">>>></span> <span class="n">bills</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"Alice"</span><span class="p">:</span> <span class="p">[</span><span class="mi">20</span><span class="p">,</span> <span class="mi">15</span><span class="p">,</span> <span class="mi">30</span><span class="p">],</span> <span class="s2">"Bob"</span><span class="p">:</span> <span class="p">[</span><span class="mi">10</span><span class="p">,</span> <span class="mi">35</span><span class="p">]}</span>
<span class="o">>>></span> <span class="n">valmap</span><span class="p">(</span><span class="nb">sum</span><span class="p">,</span> <span class="n">bills</span><span class="p">)</span>
<span class="p">{</span><span class="s1">'Alice'</span><span class="p">:</span> <span class="mi">65</span><span class="p">,</span> <span class="s1">'Bob'</span><span class="p">:</span> <span class="mi">45</span><span class="p">}</span>
</pre></div>
<p>Il y a évidemment tout un tas d'autres fonctions disponibles que je t'invite à découvrir !</p>
<p>Sinon, si tu veux pousser une peu plus loin la programmation fonctionnelle avec
python, il existe un langage intéressant appelé <a class="reference external" href="https://github.com/i2y/mochi">Mochi</a>, dont l'interpréteur
convertit le code Mochi en bytecode Python 3.</p>
<p>Il permet d'écrire notamment ce genre de chose:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">fizzbuzz</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="k">match</span> <span class="p">[</span><span class="n">n</span> <span class="o">%</span> <span class="mi">3</span><span class="p">,</span> <span class="n">n</span> <span class="o">%</span> <span class="mi">5</span><span class="p">]:</span>
<span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">]:</span> <span class="s2">"fizzbuzz"</span>
<span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="n">_</span><span class="p">]:</span> <span class="s2">"fizz"</span>
<span class="p">[</span><span class="n">_</span><span class="p">,</span> <span class="mi">0</span><span class="p">]:</span> <span class="s2">"buzz"</span>
<span class="n">_</span><span class="p">:</span> <span class="n">n</span>
<span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">31</span><span class="p">)</span>
<span class="o">|></span> <span class="nb">map</span><span class="p">(</span><span class="n">fizzbuzz</span><span class="p">)</span>
<span class="o">|></span> <span class="n">pvector</span><span class="p">()</span>
<span class="o">|></span> <span class="nb">print</span><span class="p">()</span>
</pre></div>
<p>À découvrir !</p>
Retour du FOSDEM 20162016-02-04T00:00:00+01:002016-02-04T00:00:00+01:00Morgantag:dotmobo.github.io,2016-02-04:/fosdem2016.html<p class="first last">Retour du FOSDEM 2016</p>
<img alt="FOSDEM" class="align-right" src="./images/fosdem.png" />
<p>Le <a class="reference external" href="https://fosdem.org/">FOSDEM</a> est un événement qui a lieu chaque année à
<a class="reference external" href="http://www.ulb.ac.be/">l'Université Libre de Bruxelles</a>.
Il permet de rencontrer d'autres développeurs, d'animer et
d'assister à des conférences, de partager des idées et des bières, et de
découvrir les nouvelles technos sympas du moment.</p>
<p>Cette année, c'était noir de monde ! Plus de 7900 développeurs et sysadmins étaient
présents !</p>
<p>Je vais te faire un bref petit retour des confs qui m'ont marqué et des technos
à suivre.</p>
<div class="section" id="systemd">
<h2>Systemd</h2>
<p><em>par Lennart Poettering</em></p>
<p>Ça y est, <em>systemd</em> est officiellement installé par défaut dans toutes les plus
grosses distros linux (sauf gentoo).</p>
<p>Si tu es sous linux, tu vas obligatoirement devoir te pencher dessus.
C'est grosso-modo un <em>daemon</em> qui va te permettre de gérer tes services
(apache, postgres et autres) via la commande <strong>systemctl</strong>.</p>
<p>Je ne vais pas m'éterniser dessus, car il y a déjà pas mal d'articles sur le sujet,
comme <a class="reference external" href="http://linuxfr.org/news/systemd-l-init-martyrise-l-init-bafoue-mais-l-init-libere">celui-ci</a>.</p>
</div>
<div class="section" id="docker-for-developers">
<h2>Docker for developers</h2>
<p><em>par Michael Hrivnak</em></p>
<p>Bon, n'y allons pas par quatres chemins, <a class="reference external" href="https://www.docker.com/">docker</a> est
clairement <strong>LA</strong> techno en vogue du moment. La salle était pleine à craquer, et
il y avait facilement autant de gens à l'intérieur qu'à l'extérieur. J'avais
l'impression que les organisateurs ne s'attendaient pas à ça !</p>
<p>C'est un outil écrit en go et développé par Solomon Kykes, qui permet de gérer
des conteneurs LXC et d'y déployer des applications.</p>
<p>On a pu voir ici l'utilisation quotidienne de docker pour un développeur, sans
évoquer les questions de déploiement.</p>
<p>Note : on me chuchote à l'oreille que <a class="reference external" href="https://github.com/fgrehm/vagrant-lxc">vagrant-lxc</a>
pourrait également faire l'affaire. À méditer donc !</p>
<div class="section" id="pour-les-tests-unitaires">
<h3>Pour les tests unitaires</h3>
<p>L'idée, c'est d'utiliser une image docker pour chaque combinaison
d'environnements. En python, on a déjà les <em>virtualenvs</em> qui nous
permettent de tester diverses combinaisons d'interpréteurs et de librairies
python via <em>tox</em>.</p>
<p>Grâce à docker, on va en plus pouvoir tester nos applications dans différents
environnements linux (debian, gentoo, etc...).</p>
<p>Il suffit de créer un <em>Dockerfile</em> qui va :</p>
<ul class="simple">
<li>utiliser la bonne distro.</li>
<li>installer les paquets systèmes et les paquets python nécessaires.</li>
<li>monter notre code dans <em>/code</em> par exemple.</li>
<li>exécuter les tests unitaires depuis le conteneur.</li>
</ul>
</div>
<div class="section" id="pour-la-base-de-donnees-de-dev">
<h3>Pour la base de données de dev</h3>
<p>En utilisant des conteneurs pour tes bases de données, tu peux facilement
tester une migration de postgres 9.4 en 9.5 par exemple, sans pourrir ton système.
Tu démarres un nouveau conteneur postgres 9.5, tu y fais tes tests de migration
et tu la détruis, tout simplement.</p>
<p>Tes tests unitaires pourront également utiliser une base de données dans un
conteneur, ce qui te permettra d'éviter de manipuler du <em>sqlite</em> ou une base en
mémoire.</p>
</div>
<div class="section" id="pour-diffuser-une-application-de-demo">
<h3>Pour diffuser une application de démo</h3>
<p>Ce n'est pas forcément évident de <em>containeriser</em> une application de prod.
Il faut notamment penser à la gestion des mots de passe et des certificats.</p>
<p>Mais pour diffuser une application de démo, tu peux passer outre ces questions !
Tu vas ainsi éviter à tes utilisateurs d'installer toute une application sur leur
système, en leur proposant juste de démarrer un conteneur.</p>
</div>
<div class="section" id="pour-les-serveurs-http">
<h3>Pour les serveurs http</h3>
<p>L'idée est la même que pour les bases de données. Tu peux installer et tester
<em>nginx</em>, <em>apache</em> et <em>lighttpd</em> dans des conteneurs.</p>
</div>
<div class="section" id="pour-faker-des-api">
<h3>Pour faker des API</h3>
<p>À la place de <em>mocker</em> les réponses de tes API dans tes tests unitaires, tu
peux déployer tes API de test dans un conteneur et les utiliser directement dans
tes tests unitaires.</p>
</div>
</div>
<div class="section" id="guix-tox">
<h2>Guix-tox</h2>
<p><em>par Cyril Roelandt</em></p>
<p><a class="reference external" href="https://www.gnu.org/software/guix/">Guix</a> est un gestionnaire fonctionnel de
paquets pour le système GNU.</p>
<p>Il fonctionne pour tous les langages. Ainsi, il permettrait d'éviter d'utiliser
des gestionnaires de paquets spécifiques aux langages, comme pip, npm, cpan.</p>
<p>Il utilise le langage <a class="reference external" href="http://www.gnu.org/software/guile/">guile</a>, qui est
une implémentation de <a class="reference external" href="http://schemers.org/">scheme</a>.</p>
<p>Dans guix, on a entre autres la possibilité d'utiliser des conteneurs, de gérer
des profils, de revenir en arrière.</p>
<p>Je t'invite à lire <a class="reference external" href="http://matutine.cmoi.cc/2015/11/14/installer-guix-le-gestionnaire-de-paquets-distro-venv-universel-et-container.html">l'article de Matutine</a>
qui explique bien l'idée générale.</p>
<p>Et du coup, <a class="reference external" href="https://git.framasoft.org/Steap/guix-tox">guix-tox</a> est tout
simplement un fork de tox qui remplace les <em>virtualenvs</em> python par guix.</p>
<p>Tu vas ainsi pouvoir tester ton application dans un environnement système
complet, ce qui rejoint l'idée précédemment évoquée avec docker.</p>
<p>Le débat entre guix, docker et vagrant reste ouvert !</p>
</div>
<div class="section" id="pulp">
<h2>Pulp</h2>
<p><em>par Michael Hrivnak</em></p>
<p><a class="reference external" href="http://www.pulpproject.org/">Pulp</a> est un projet qui permet de gérer ses propres
dépôts. On va ainsi pouvoir héberger son propre pypi, synchroniser les dépôts
entre eux et y uploader nos paquets.</p>
<p>C'est compatible avec les paquets debian, rpm, python, docker et autres.
Franchement, ça semble plutôt bien foutu et pratique!</p>
<p>Pour finir, merci à toute l'équipe du FOSDEM et à l'année prochaine !</p>
</div>
Se passer de Grunt/Gulp/Brunch/Broccoli/Mimosa/Jake grâce à Make2016-01-12T00:00:00+01:002016-01-12T00:00:00+01:00Morgantag:dotmobo.github.io,2016-01-12:/makefile-frontend.html<p class="first last">Se passer de Grunt/Gulp/Brunch/Broccoli/Mimosa/Jake grâce à Make</p>
<img alt="Gnu" class="align-right" src="./images/gnu.png" />
<p>La communauté <a class="reference external" href="https://nodejs.org">Node.js</a> a été plutôt productive ces deux
dernières années, notamment au niveau de la création de <em>tasks runners</em>.</p>
<p>Certains sont enthousiastes de cet essor et de cette profusion d'outils,
mais d'autres le sont beaucoup moins <a class="reference external" href="https://medium.com/@wob/the-sad-state-of-web-development-1603a861d29f#.nrikd9bai">et le font savoir</a>.</p>
<p>Tu as forcément déjà lu un article ou vu un bout de code qui mentionnait au choix <a class="reference external" href="http://gruntjs.com/">grunt</a>,
<a class="reference external" href="http://gulpjs.com/">gulp</a>, <a class="reference external" href="http://brunch.io/">brunch</a>, <a class="reference external" href="http://broccolijs.com/">broccoli</a>,
<a class="reference external" href="http://mimosa.io/">mimosa</a>, <a class="reference external" href="http://jakejs.com/">jake</a> ou que sais-je.</p>
<p>Mais lequel utiliser ? Pourquoi y en a-t-il autant ? Quel est le plus simple ? Le plus performant ? Le plus suivi ?
Quel tuto suivre ?</p>
<p>Franchement, devoir ré-apprendre un <em>tasks runners</em> tous les trois mois en fonction de la mode,
c'est plutôt lourd. Et ça rappelle fortement le choix cornélien du framework ou de la librairie javascript (
<a class="reference external" href="https://angularjs.org/">angular</a>, <a class="reference external" href="https://angular.io/">angular2</a>, <a class="reference external" href="https://facebook.github.io/react/">react</a>,
<a class="reference external" href="https://www.polymer-project.org/1.0/">polymer</a>, <a class="reference external" href="https://jquery.com/">jquery</a>, <a class="reference external" href="http://vuejs.org/">vue.js</a>, etc...)</p>
<p>N'y a-t-il pas un outil robuste, simple et pérenne ? Et qui pourrait te servir pour autre chose que du
développement frontend tant qu'à faire ? Et ben si, ça existe depuis la création de <a class="reference external" href="https://www.gnu.org">GNU</a> en 1983 par Richard
Stallman, et ça s'appelle <a class="reference external" href="https://www.gnu.org/software/make/">Make</a>.</p>
<p><em>Make</em> est un outil qui permet d'automatiser la construction de fichiers et qui peut
jouer le rôle de <em>tasks runners</em>.</p>
<p>Concrètement, en développement web, de quoi est-ce qu'on a le plus besoin ?</p>
<ul class="simple">
<li>Compiler nos fichiers <a class="reference external" href="http://lesscss.org/">LESS</a> ou <a class="reference external" href="http://sass-lang.com/">Sass</a> en CSS.</li>
<li>Minifier nos fichiers CSS.</li>
<li>Compiler nos fichiers <a class="reference external" href="http://livescript.net/">LiveScript</a>, <a class="reference external" href="http://coffeescript.org/">CoffeeScript</a> ou <a class="reference external" href="http://www.typescriptlang.org/">TypeScript</a> en JavaScript.</li>
<li>Minifier nos fichiers JavaScript.</li>
<li>Avoir un <em>watcher</em> qui lance automatiquement toutes ces tâches lorsqu'un fichier source est modifié.</li>
</ul>
<p>On suppose que tu as :</p>
<ul class="simple">
<li>tes fichiers LESS dans <strong>static/src/less</strong></li>
<li>tes fichiers LiveScript dans <strong>static/src/ls</strong></li>
</ul>
<p>Et que tu veux tes fichiers compilés et minifiés dans <strong>static/dist/css</strong> et <strong>static/dist/js</strong>.</p>
<p>C'est parti, tu vas te créer un fichier <strong>Makefile</strong> qui fera tous ça !</p>
<div class="highlight"><pre><span></span>vim Makefile
</pre></div>
<p>Tu y déclares tes répertoires de travail :</p>
<div class="highlight"><pre><span></span>LESS_FOLDER :<span class="o">=</span> static/src/less
LS_FOLDER :<span class="o">=</span> static/src/ls
CSS_FOLDER :<span class="o">=</span> static/dist/css
JS_FOLDER :<span class="o">=</span> static/dist/js
</pre></div>
<p>Ensuite, tu y listes les fichiers concernés :</p>
<div class="highlight"><pre><span></span>LESS_FILES :<span class="o">=</span> <span class="si">${</span><span class="nv">shell</span><span class="p"> find </span><span class="si">${</span><span class="nv">LESS_FOLDER</span><span class="si">}</span><span class="p"> -type f -name </span><span class="s1">'*.less'</span><span class="si">}</span>
LS_FILES :<span class="o">=</span> <span class="si">${</span><span class="nv">shell</span><span class="p"> find </span><span class="si">${</span><span class="nv">LS_FOLDER</span><span class="si">}</span><span class="p"> -type f -name </span><span class="s1">'*.ls'</span><span class="si">}</span>
CSS_FILE :<span class="o">=</span> <span class="si">${</span><span class="nv">CSS_FOLDER</span><span class="si">}</span>/main.min.css
JS_FILE :<span class="o">=</span> <span class="si">${</span><span class="nv">JS_FOLDER</span><span class="si">}</span>/main.min.js
</pre></div>
<p>La commande <strong>find</strong> utilisée pour <em>LESS_FILES</em> et <em>LS_FILES</em> permet de récupérer le
chemin de tous les fichiers LESS et LiveScript présents.</p>
<p>Tu déclares une tâche <em>build</em> qui sera chargée de lancer la compilation des fichiers :</p>
<div class="highlight"><pre><span></span>build: build-css build-js
</pre></div>
<p>Puis, tu commences par créer la tâche qui va compiler les fichiers LESS :</p>
<div class="highlight"><pre><span></span>build-css: <span class="si">${</span><span class="nv">CSS_FOLDER</span><span class="si">}</span> <span class="si">${</span><span class="nv">CSS_FILE</span><span class="si">}</span>
<span class="si">${</span><span class="nv">CSS_FILE</span><span class="si">}</span>: <span class="si">${</span><span class="nv">LESS_FILES</span><span class="si">}</span>
cat $^ <span class="p">|</span> lessc - <span class="p">|</span> cleancss > <span class="nv">$@</span>
<span class="si">${</span><span class="nv">CSS_FOLDER</span><span class="si">}</span>:
mkdir -p <span class="nv">$@</span>
</pre></div>
<p>Ta tâche <em>build-css</em> va créer le répertoire <strong>static/dist/css</strong> via <strong>mkdir</strong>.
Et elle va compiler tous les fichiers LESS à l'aide de <strong>lessc</strong> et les minifier à
l'aide de <strong>cleancss</strong>.</p>
<p>Dans une <strong>Makefile</strong>, <em>$^</em> correspond à la liste des dépendances. Il s'agit ici
des fichiers contenus dans <em>LESS_FILES</em>. <em>$@</em> correspond au nom de la cible, à
savoir le fichier contenu dans <em>CSS_FILE</em>.</p>
<p>De la même manière, tu crées la tâche qui va compiler les fichiers LiveScript :</p>
<div class="highlight"><pre><span></span>build-js: <span class="si">${</span><span class="nv">JS_FOLDER</span><span class="si">}</span> <span class="si">${</span><span class="nv">JS_FILE</span><span class="si">}</span>
<span class="si">${</span><span class="nv">JS_FILE</span><span class="si">}</span>: <span class="si">${</span><span class="nv">LS_FILES</span><span class="si">}</span>
cat $^ <span class="p">|</span> lsc -sc <span class="p">|</span> uglifyjs - > <span class="nv">$@</span>
<span class="si">${</span><span class="nv">JS_FOLDER</span><span class="si">}</span>:
mkdir -p <span class="nv">$@</span>
</pre></div>
<p>Les fichiers LiveScript sont compilés à l'aide de <strong>lsc</strong> et minifiés à l'aide
de <strong>uglifyjs</strong>.</p>
<p>Tu rajoutes une méthode pour nettoyer le répertoire <em>dist</em> :</p>
<div class="highlight"><pre><span></span>clean:
rm <span class="si">${</span><span class="nv">CSS_FILE</span><span class="si">}</span> <span class="si">${</span><span class="nv">JS_FILE</span><span class="si">}</span>
</pre></div>
<p>Et une méthode qui te permettra d'installer les librairies utilisées par ton <strong>Makefile</strong> via <strong>npm</strong> :</p>
<div class="highlight"><pre><span></span>install-dependencies:
sudo npm install -g less clean-css livescript uglifyjs onchange
</pre></div>
<p>Enfin, tu vas mettre en place ton <em>watcher</em> à l'aide de <strong>onchange</strong> :</p>
<div class="highlight"><pre><span></span>watch:
onchange <span class="s2">"</span><span class="si">${</span><span class="nv">LESS_FILES</span><span class="si">}</span><span class="s2">"</span> <span class="s2">"</span><span class="si">${</span><span class="nv">LS_FILES</span><span class="si">}</span><span class="s2">"</span> -- make build
</pre></div>
<p>La dernière instruction servira à dire à <strong>make</strong> que certaines tâches
ne sont pas directement liées à des noms de fichiers :</p>
<div class="highlight"><pre><span></span>.PHONY: build build-css build-js clean watch install-dependencies
</pre></div>
<p>Et c'est fini! Tu disposes désormais des commandes suivantes :</p>
<ul class="simple">
<li><strong>make install-dependencies</strong> : pour installer les dépendances.</li>
<li><strong>make</strong> ou <strong>make build</strong> : pour compiler et minifier tous les fichiers.</li>
<li><strong>make build-js</strong> : pour compiler et minifier les fichiers LiveScript.</li>
<li><strong>make build-css</strong> : pour compiler et minifier les fichiers LESS.</li>
<li><strong>make clean</strong> : pour effacer les fichiers compilés et minifiés.</li>
<li><strong>make watch</strong> : pour relancer automatiquement la commande <strong>make build</strong> à chaque modifications de fichiers.</li>
</ul>
<p>Le résultat final :</p>
<div class="highlight"><pre><span></span><span class="c1">#################################################</span>
<span class="c1"># Compile and minify less and livescript files. #</span>
<span class="c1"># Require node.js and npm #</span>
<span class="c1">#################################################</span>
LESS_FOLDER :<span class="o">=</span> static/src/less
LS_FOLDER :<span class="o">=</span> static/src/ls
CSS_FOLDER :<span class="o">=</span> static/dist/css
JS_FOLDER :<span class="o">=</span> static/dist/js
LESS_FILES :<span class="o">=</span> <span class="si">${</span><span class="nv">shell</span><span class="p"> find </span><span class="si">${</span><span class="nv">LESS_FOLDER</span><span class="si">}</span><span class="p"> -type f -name </span><span class="s1">'*.less'</span><span class="si">}</span>
LS_FILES :<span class="o">=</span> <span class="si">${</span><span class="nv">shell</span><span class="p"> find </span><span class="si">${</span><span class="nv">LS_FOLDER</span><span class="si">}</span><span class="p"> -type f -name </span><span class="s1">'*.ls'</span><span class="si">}</span>
CSS_FILE :<span class="o">=</span> <span class="si">${</span><span class="nv">CSS_FOLDER</span><span class="si">}</span>/main.min.css
JS_FILE :<span class="o">=</span> <span class="si">${</span><span class="nv">JS_FOLDER</span><span class="si">}</span>/main.min.js
<span class="c1">###############</span>
<span class="c1"># Build files #</span>
<span class="c1">###############</span>
build: build-css build-js
<span class="c1">####################</span>
<span class="c1"># Build css files #</span>
<span class="c1">####################</span>
build-css: <span class="si">${</span><span class="nv">CSS_FOLDER</span><span class="si">}</span> <span class="si">${</span><span class="nv">CSS_FILE</span><span class="si">}</span>
<span class="si">${</span><span class="nv">CSS_FILE</span><span class="si">}</span>: <span class="si">${</span><span class="nv">LESS_FILES</span><span class="si">}</span>
cat $^ <span class="p">|</span> lessc - <span class="p">|</span> cleancss > <span class="nv">$@</span>
<span class="si">${</span><span class="nv">CSS_FOLDER</span><span class="si">}</span>:
mkdir -p <span class="nv">$@</span>
<span class="c1">##################</span>
<span class="c1"># Build js files #</span>
<span class="c1">##################</span>
build-js: <span class="si">${</span><span class="nv">JS_FOLDER</span><span class="si">}</span> <span class="si">${</span><span class="nv">JS_FILE</span><span class="si">}</span>
<span class="si">${</span><span class="nv">JS_FILE</span><span class="si">}</span>: <span class="si">${</span><span class="nv">LS_FILES</span><span class="si">}</span>
cat $^ <span class="p">|</span> lsc -sc <span class="p">|</span> uglifyjs - > <span class="nv">$@</span>
<span class="si">${</span><span class="nv">JS_FOLDER</span><span class="si">}</span>:
mkdir -p <span class="nv">$@</span>
<span class="c1">###############</span>
<span class="c1"># Clean files #</span>
<span class="c1">###############</span>
clean:
rm <span class="si">${</span><span class="nv">CSS_FILE</span><span class="si">}</span> <span class="si">${</span><span class="nv">JS_FILE</span><span class="si">}</span>
<span class="c1">########################</span>
<span class="c1"># Install dépendencies #</span>
<span class="c1">########################</span>
install-dependencies:
sudo npm install -g less clean-css livescript uglifyjs onchange
<span class="c1">###############</span>
<span class="c1"># Watch files #</span>
<span class="c1">###############</span>
watch:
onchange <span class="s2">"</span><span class="si">${</span><span class="nv">LESS_FILES</span><span class="si">}</span><span class="s2">"</span> <span class="s2">"</span><span class="si">${</span><span class="nv">LS_FILES</span><span class="si">}</span><span class="s2">"</span> -- make build
<span class="c1">#########</span>
<span class="c1"># Phony #</span>
<span class="c1">#########</span>
.PHONY: build build-css build-js clean watch install-dependencies
</pre></div>
<p>Evidemment, ça reste un exemple très simple. Mais rien ne t'empêche de faire
évoluer ton <strong>Makefile</strong> selon tes besoins.</p>
<p>En plus, tu viens d'apprendre un outil qui te sera utile pour faire de l'administration système.</p>
<p>Bonne compilation !</p>
Templating avec Mako2015-12-23T00:00:00+01:002015-12-23T00:00:00+01:00Morgantag:dotmobo.github.io,2015-12-23:/mako.html<p class="first last">Templating avec mako</p>
<img alt="Mako" class="align-right" src="./images/mako.png" />
<p><em>Python is a great scripting language. Don't reinvent the wheel...
your templates can handle it !</em></p>
<p>Je suis tombé sur <a class="reference external" href="http://www.makotemplates.org/">mako</a> suite à une discussion
mouvementée au sujet de <a class="reference external" href="https://www.djangoproject.com/">django</a> et de
<a class="reference external" href="http://jinja.pocoo.org/">jinja</a> avec un de mes collègues pro
<a class="reference external" href="http://www.groovy-lang.org/">groovy</a>/<a class="reference external" href="https://grails.org/">grails</a>.</p>
<p>Ça devait ressembler à quelque-chose comme ça:</p>
<div class="line-block">
<div class="line"><em>- Rhhaaa je déteste jinja, j'suis obligé d'me taper la doc pour apprendre toute la syntaxe! En plus c'est grave limité! Y a pas moyen d'y écrire directement du python ?</em></div>
<div class="line"><em>- Bah ça va, c'est pas trop compliqué, et si t'as besoin d'utiliser des méthodes python, tu peux utiliser les custom filters et custom tags!</em></div>
<div class="line"><em>- Uai mais c'est pas super pratique, avec grails tu peux directement mettre du groovy dans tes templates, c'est génial blablabla</em></div>
<div class="line"><em>- Ok ok j'ai compris, jinja te convient pas, mais j'suis sûr que ce que tu cherches existe dans la communauté python! Voyons voir ... regardes, y a un truc là, ça s'appelle mako!</em></div>
<div class="line"><em>- *Yeux qui brillent*</em></div>
<div class="line"><br /></div>
</div>
<p>Mako est un moteur de template utilisé dans le framework
<a class="reference external" href="http://www.pylonsproject.org/">pyramid</a>, à l'instar de jinja utilisé dans django.
Il est réputé pour sa performance, et est même utilisé pour l'affichage des pages
de <a class="reference external" href="https://www.reddit.com/">reddit</a>!</p>
<p>Mais son principal atout est de pouvoir utiliser directement du python à l'aide
des balises <strong><% %></strong> dans les templates.</p>
<p>Certains diront que c'est pas génial, car on peut vite obtenir des templates
assez crades avec tout et n'importe quoi dedans.
D'autres diront que ça ne sert à rien d'inventer une nouvelle syntaxe spécifique
aux templates, alors que python est disponible et peut faire ça très bien.</p>
<p>Pour te faire ton propre avis, installes-le via pip:</p>
<div class="highlight"><pre><span></span>pip install mako
</pre></div>
<p>En résumé, tes templates ressembleront à ça:</p>
<div class="highlight"><pre><span></span><span class="o"><%</span><span class="n">inherit</span> <span class="n">file</span><span class="o">=</span><span class="s2">"base.html"</span><span class="o">/></span>
<span class="o"><%</span>
<span class="n">rows</span> <span class="o">=</span> <span class="p">[[</span><span class="n">v</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">)]</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">)]</span>
<span class="o">%></span>
<span class="o"><</span><span class="n">table</span><span class="o">></span>
<span class="o">%</span> <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">rows</span><span class="p">:</span>
<span class="err">$</span><span class="p">{</span><span class="n">makerow</span><span class="p">(</span><span class="n">row</span><span class="p">)}</span>
<span class="o">%</span> <span class="n">endfor</span>
<span class="o"></</span><span class="n">table</span><span class="o">></span>
<span class="o"><%</span><span class="k">def</span> <span class="nf">name</span><span class="o">=</span><span class="s2">"makerow(row)"</span><span class="o">></span>
<span class="o"><</span><span class="n">tr</span><span class="o">></span>
<span class="o">%</span> <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">row</span><span class="p">:</span>
<span class="o"><</span><span class="n">td</span><span class="o">></span><span class="err">$</span><span class="p">{</span><span class="n">name</span><span class="p">}</span><span class="o"></</span><span class="n">td</span><span class="o">></span>\
<span class="o">%</span> <span class="n">endfor</span>
<span class="o"></</span><span class="n">tr</span><span class="o">></span>
<span class="o"></%</span><span class="n">def</span><span class="o">></span>
</pre></div>
<p>La syntaxe de mako est très bien expliquée dans la
<a class="reference external" href="http://docs.makotemplates.org/en/latest/syntax.html">doc officielle</a>,
mais pour faire simple :</p>
<ul class="simple">
<li>La substitution d'expressions utilise le symbole <strong>${}</strong>, à la manière de perl.</li>
<li>Tu disposes de <a class="reference external" href="http://docs.makotemplates.org/en/latest/filtering.html">filtres</a> comme sous jinja, via une syntaxe type <strong>${"this is some text" | u}</strong></li>
<li>Il y a les structures de contrôle classiques <strong>% if</strong> et <strong>% else</strong> et les boucles avec <strong>% for</strong>.</li>
<li>Divers tags sont disponibles comme <strong><%inhérit/></strong>, <strong><%page/></strong>, <strong><%include/></strong>, etc ...</li>
<li>Tu peux rajouter des commentaires avec <strong>##</strong>.</li>
<li>Enfin, tout ce qui se trouve entre <strong><% %></strong> est du python. Tu peux profiter de toute la syntaxe de python et même importer des libs. C'est la fête !</li>
</ul>
<p>Et pour l'utiliser, rien de plus simple, tu utilises la classe <strong>Template</strong> :</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">mako.template</span> <span class="kn">import</span> <span class="n">Template</span>
<span class="n">mytemplate</span> <span class="o">=</span> <span class="n">Template</span><span class="p">(</span><span class="s2">"hello, $</span><span class="si">{name}</span><span class="s2">!"</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">mytemplate</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">"jack"</span><span class="p">))</span>
</pre></div>
<p>Tu peux désormais utiliser mako pour gérer tes pages html! Et si tu es un
utilisateur de django, il t'es même possible de remplacer jinja par mako via
<a class="reference external" href="https://github.com/doconix/django-mako-plus">django-mako-plus</a>.</p>
<p>En dehors des pages html, tu peux également l'utiliser <a class="reference external" href="http://stackoverflow.com/questions/3049188/generating-very-large-xml-files-in-python">pour générer d'énormes
fichiers XML simples</a>,
comme une liste d'utilisateurs par exemple.
Ça évite de manipuler le DOM en mémoire comme avec <a class="reference external" href="http://lxml.de/">lxml</a>,
c'est plus facile d'accès que <a class="reference external" href="https://docs.python.org/3/library/xml.sax.html">SAX</a>,
et c'est très performant :</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">mako.template</span> <span class="kn">import</span> <span class="n">Template</span>
<span class="kn">from</span> <span class="nn">mako.runtime</span> <span class="kn">import</span> <span class="n">Context</span>
<span class="n">tpl_xml</span> <span class="o">=</span> <span class="s1">'''</span>
<span class="s1"><doc></span>
<span class="si">% f</span><span class="s1">or i in data:</span>
<span class="s1"><p>$</span><span class="si">{i}</span><span class="s1"></p></span>
<span class="si">% e</span><span class="s1">ndfor</span>
<span class="s1"></doc></span>
<span class="s1">'''</span>
<span class="n">tpl</span> <span class="o">=</span> <span class="n">Template</span><span class="p">(</span><span class="n">tpl_xml</span><span class="p">)</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s1">'output.xml'</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">ctx</span> <span class="o">=</span> <span class="n">Context</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">xrange</span><span class="p">(</span><span class="mi">10000000</span><span class="p">))</span>
<span class="n">tpl</span><span class="o">.</span><span class="n">render_context</span><span class="p">(</span><span class="n">ctx</span><span class="p">)</span>
</pre></div>
Httpie, le client http intuitif2015-12-08T00:00:00+01:002015-12-08T00:00:00+01:00Morgantag:dotmobo.github.io,2015-12-08:/httpie.html<p class="first last">Httpie, le client http intuitif</p>
<img alt="Httpie" class="align-right" src="./images/http.png" />
<p><a class="reference external" href="http://httpie.org">Httpie</a> est un client http en ligne de commande plutôt
bien foutu. C'est une alternative viable à <a class="reference external" href="http://curl.haxx.se/">curl</a> qui
a été développée en python.</p>
<p>Intuitif, simple, et prenant en charge la coloration syntaxique, c'est l'outil
idéal pour requêter des APIs JSON par exemple.</p>
<p>Tu l'installes avec pip :</p>
<div class="highlight"><pre><span></span>pip install httpie
</pre></div>
<p>Tu disposes maintenant de la commande <strong>http</strong> dans ton terminal qui te
permettra d'effectuer des requêtes très simplement de cette manière:</p>
<div class="highlight"><pre><span></span>http <span class="o">[</span>flags<span class="o">]</span> <span class="o">[</span>METHOD<span class="o">]</span> URL <span class="o">[</span>ITEM <span class="o">[</span>ITEM<span class="o">]]</span>
</pre></div>
<p>Tu peux évidemment faire du GET:</p>
<div class="highlight"><pre><span></span>http httpie.org
http GET httpie.org
</pre></div>
<p>Du DELETE:</p>
<div class="highlight"><pre><span></span>http DELETE example.org/todos/7
</pre></div>
<p>Ou mettre à jour les données d'une API via PUT:</p>
<div class="highlight"><pre><span></span>http PUT example.org X-API-Token:123 <span class="nv">name</span><span class="o">=</span>John
</pre></div>
<p>Tu remarques ici qu'on a d'abord passé la méthode PUT, puis l'url, puis le token
d'authentification dans l'entête de la requête, et enfin les données.</p>
<p>Pas besoin d'utiliser des options pour spécifier chaque élément, tout se fait intuitivement !
Les éléments de l'entête de la requête sont séparés par <strong>:</strong>, les données par
<strong>=</strong>.</p>
<p>Tu peux aussi utiliser l'option <strong>-a</strong> pour l'authentification type
username/password.</p>
<p>Par exemple, pour poster un commentaire sur un problème via l'api github:</p>
<div class="highlight"><pre><span></span>http -a USERNAME POST https://api.github.com/repos/jkbrzt/httpie/issues/83/comments <span class="nv">body</span><span class="o">=</span><span class="s1">'HTTPie is awesome!'</span>
</pre></div>
<p>Tu peux utiliser les symboles de redirection pour uploader
ou downloader des fichiers :</p>
<div class="highlight"><pre><span></span>http example.org < file.json
http example.org > file.json
</pre></div>
<p>L'outil est très complet (gestion des sessions, des certificats SSL) mais je
l'apprécie surtout pour son côté simple et facile d'accès.</p>
<p>Tu vas pouvoir manipuler et tester tes APIs très rapidement, sans te prendre la
tête avec une documentation imbuvable!</p>
Ajouter un captcha dans un formulaire django2015-11-28T00:00:00+01:002015-11-28T00:00:00+01:00Morgantag:dotmobo.github.io,2015-11-28:/django-simple-captcha.html<p class="first last">Ajouter un captcha dans un formulaire django</p>
<img alt="Django" class="align-right" src="./images/djangopony.png" />
<p>Suite à <a class="reference external" href="http://dotmobo.github.io/django-countries.html">l'article sur django-countries</a>,
on va continuer notre parcours des petits outils simples et efficaces pour améliorer nos formulaires django.</p>
<p>Avant d'aborder le gros morceau qu'est <a class="reference external" href="http://django-crispy-forms.readthedocs.org/en/latest/">django-cryspy</a>
(tu peux déjà y jeter un oeil si tu es curieux), on va parler de l'intégration de captchas sous django.</p>
<p>L'api de captcha la plus connue est sûrement <a class="reference external" href="https://www.google.com/recaptcha">reCAPTCHA</a> de google,
et il en existe une implémentation sous django baptisée <a class="reference external" href="https://github.com/praekelt/django-recaptcha">django-recaptcha</a>.</p>
<p>Mais tu ne veux peut-être pas dépendre d'un service google pour diverses raisons, en préférant
une solution autonome, simple, légère, efficace et maintenue.</p>
<p><a class="reference external" href="https://github.com/mbi/django-simple-captcha">Django-simple-captcha</a> remplit haut la main toutes ces conditions
et est même compatible python 3. Elle est customisable, s'intègre dans les formulaires django et sous
<em>django-crispy</em>. Différents types de captcha sont disponibles comme des caractères aléatoires,
des calculs mathématiques, des dictionnaires de mots.</p>
<p>Captcha utilise <em>PIL</em> et <em>Pillow</em> qui nécessitent certaines librairies systèmes.
Par exemple sous Ubuntu, tu fais:</p>
<div class="highlight"><pre><span></span>apt-get -y install libz-dev libjpeg-dev libfreetype6-dev
</pre></div>
<p>Tu l'installes comme d'habitude avec pip:</p>
<div class="highlight"><pre><span></span>pip install django-simple-captcha
</pre></div>
<p>Et tu ajoutes <strong>captcha</strong> dans la liste de tes <strong>INSTALLED_APPS</strong>
dans le fichier <em>settings.py</em> de django:</p>
<div class="highlight"><pre><span></span><span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">(</span><span class="o">...</span><span class="p">,</span>
<span class="n">captcha</span>
<span class="p">)</span>
</pre></div>
<p>Tu synchronises ta base de données:</p>
<div class="highlight"><pre><span></span>python manage.py migrate
</pre></div>
<p>Et tu ajoutes l'entrée suivante dans ton fichier <strong>urls.py</strong>:</p>
<div class="highlight"><pre><span></span><span class="n">urlpatterns</span> <span class="o">+=</span> <span class="n">patterns</span><span class="p">(</span><span class="s1">''</span><span class="p">,</span>
<span class="n">url</span><span class="p">(</span><span class="sa">r</span><span class="s1">'^captcha/'</span><span class="p">,</span> <span class="n">include</span><span class="p">(</span><span class="s1">'captcha.urls'</span><span class="p">)),</span>
<span class="p">)</span>
</pre></div>
<p>Il ne reste plus qu'à l'intégrer dans ton formulaire.</p>
<ul class="simple">
<li>Soit dans un <strong>Form</strong>:</li>
</ul>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django</span> <span class="kn">import</span> <span class="n">forms</span>
<span class="kn">from</span> <span class="nn">captcha.fields</span> <span class="kn">import</span> <span class="n">CaptchaField</span>
<span class="k">class</span> <span class="nc">CaptchaTestForm</span><span class="p">(</span><span class="n">forms</span><span class="o">.</span><span class="n">Form</span><span class="p">):</span>
<span class="n">myfield</span> <span class="o">=</span> <span class="n">AnyOtherField</span><span class="p">()</span>
<span class="n">captcha</span> <span class="o">=</span> <span class="n">CaptchaField</span><span class="p">()</span>
</pre></div>
<ul class="simple">
<li>Soit dans un <strong>ModelForm</strong>:</li>
</ul>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django</span> <span class="kn">import</span> <span class="n">forms</span>
<span class="kn">from</span> <span class="nn">captcha.fields</span> <span class="kn">import</span> <span class="n">CaptchaField</span>
<span class="k">class</span> <span class="nc">CaptchaTestModelForm</span><span class="p">(</span><span class="n">forms</span><span class="o">.</span><span class="n">ModelForm</span><span class="p">):</span>
<span class="n">captcha</span> <span class="o">=</span> <span class="n">CaptchaField</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">MyModel</span>
</pre></div>
<p>Et sous <em>django-crispy</em>, tu peux utiliser <strong>captcha</strong> comme un <strong>Field</strong> à déclarer dans le <strong>Layout</strong>:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django</span> <span class="kn">import</span> <span class="n">forms</span>
<span class="kn">from</span> <span class="nn">captcha.fields</span> <span class="kn">import</span> <span class="n">CaptchaField</span>
<span class="kn">from</span> <span class="nn">crispy_forms.helper</span> <span class="kn">import</span> <span class="n">FormHelper</span>
<span class="kn">from</span> <span class="nn">crispy_forms.layout</span> <span class="kn">import</span> <span class="n">Layout</span><span class="p">,</span> <span class="n">Field</span><span class="p">,</span> <span class="n">Submit</span>
<span class="k">class</span> <span class="nc">CaptchaTestModelForm</span><span class="p">(</span><span class="n">forms</span><span class="o">.</span><span class="n">ModelForm</span><span class="p">):</span>
<span class="n">captcha</span> <span class="o">=</span> <span class="n">CaptchaField</span><span class="p">()</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">helper</span> <span class="o">=</span> <span class="n">FormHelper</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">helper</span><span class="o">.</span><span class="n">layout</span> <span class="o">=</span> <span class="n">Layout</span><span class="p">(</span>
<span class="n">Field</span><span class="p">(</span><span class="s1">'name'</span><span class="p">,</span> <span class="n">placeholder</span><span class="o">=</span><span class="s2">"Enter Full Name"</span><span class="p">),</span>
<span class="n">Field</span><span class="p">(</span><span class="s1">'captcha '</span><span class="p">,</span> <span class="n">placeholder</span><span class="o">=</span><span class="s2">"Enter captcha"</span><span class="p">),</span>
<span class="n">Submit</span><span class="p">(</span><span class="s1">'valid'</span><span class="p">,</span> <span class="s1">'Valid'</span><span class="p">)</span>
<span class="p">)</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">MyModel</span>
</pre></div>
<p>La validation du formulaire se fait comme d'habitude. Si la réponse au captcha
est mauvaise, une exception <strong>ValidationError</strong> sera levée:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">some_view</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">POST</span><span class="p">:</span>
<span class="n">form</span> <span class="o">=</span> <span class="n">CaptchaTestForm</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">POST</span><span class="p">)</span>
<span class="c1"># Validate the form: the captcha field will automatically</span>
<span class="c1"># check the input</span>
<span class="k">if</span> <span class="n">form</span><span class="o">.</span><span class="n">is_valid</span><span class="p">():</span>
<span class="n">human</span> <span class="o">=</span> <span class="kc">True</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">form</span> <span class="o">=</span> <span class="n">CaptchaTestForm</span><span class="p">()</span>
<span class="k">return</span> <span class="n">render_to_response</span><span class="p">(</span><span class="s1">'template.html'</span><span class="p">,</span><span class="nb">locals</span><span class="p">())</span>
</pre></div>
<p>Il est également possible de faire une validation
<a class="reference external" href="http://django-simple-captcha.readthedocs.org/en/latest/usage.html#example-usage-for-ajax-form">via ajax</a>.</p>
<p>Il y a toute <a class="reference external" href="http://django-simple-captcha.readthedocs.org/en/latest/advanced.html">une série de paramètres</a>
permettant la customisation du captcha, mais il y en a surtout deux à retenir.</p>
<p>Le premier permet de choisir le type de captcha que tu veux utiliser.
Dans ton <strong>settings.py</strong>, tu peux mettre au choix:</p>
<div class="highlight"><pre><span></span><span class="c1"># Random chars</span>
<span class="n">CAPTCHA_CHALLENGE_FUNCT</span> <span class="o">=</span> <span class="s1">'captcha.helpers.random_char_challenge'</span>
<span class="c1"># Simple Math</span>
<span class="n">CAPTCHA_CHALLENGE_FUNCT</span> <span class="o">=</span> <span class="s1">'captcha.helpers.math_challenge'</span>
<span class="c1"># Dictionary Word</span>
<span class="n">CAPTCHA_CHALLENGE_FUNCT</span> <span class="o">=</span> <span class="s1">'captcha.helpers.word_challenge'</span>
</pre></div>
<p>Tu peux même créer <a class="reference external" href="http://django-simple-captcha.readthedocs.org/en/latest/advanced.html#roll-your-own">ta propre fonction de captcha</a>
si celles proposées ne te conviennent pas.</p>
<p>Le second permet d'utiliser le captcha dans les tests unitaires. Si la chaîne de caractères <strong>PASSED</strong>
est renseignée comme valeur de réponse au captcha, le formulaire sera valide.
À mettre dans ton fichier de configuration de tes tests unitaires:</p>
<div class="highlight"><pre><span></span><span class="n">CAPTCHA_TEST_MODE</span> <span class="o">=</span> <span class="kc">True</span>
</pre></div>
<p>Le résultat final ressemble à ça :</p>
<img alt="Django" class="align-left" src="http://django-simple-captcha.readthedocs.org/en/latest/_images/captcha3.png" />
Upsert avec Postgresql2015-11-22T00:00:00+01:002015-11-22T00:00:00+01:00Morgantag:dotmobo.github.io,2015-11-22:/upsert-postgresql.html<p class="first last">Upsert avec Postgresql</p>
<img alt="Postgresql" class="align-right" src="./images/postgresql.png" />
<p>Lorsque l'on commence à <a class="reference external" href="http://dotmobo.github.io/introduction-asyncio.html">s'amuser avec asyncio</a>
pour faire des traitements asynchrones sur une base <a class="reference external" href="http://www.postgresqlfr.org/">postgresql</a>
avec <a class="reference external" href="https://github.com/aio-libs/aiopg">aiopg</a>, on rencontre assez vite le problème des accès concurrents
à une ressource partagée.</p>
<p>Et notamment lorsqu'il s'agit d'appliquer de multiples <em>insert</em> ou <em>update</em>, appelés <em>upsert</em> dans le monde de <em>mongodb</em>.</p>
<p>Sous <em>mongodb</em> avec <a class="reference external" href="https://api.mongodb.org/python/current/">pymongo</a> c'est facile,
il suffit de passer le paramètre <em>upsert=True</em> à la méthode <em>update_one</em> :</p>
<div class="highlight"><pre><span></span><span class="o">>></span> <span class="kn">from</span> <span class="nn">pymongo</span> <span class="kn">import</span> <span class="n">MongoClient</span>
<span class="o">>></span> <span class="n">client</span> <span class="o">=</span> <span class="n">MongoClient</span><span class="p">()</span>
<span class="o">>></span> <span class="n">db</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">test_database</span>
<span class="o">>></span> <span class="n">db</span><span class="o">.</span><span class="n">test</span><span class="o">.</span><span class="n">update_one</span><span class="p">({</span><span class="s1">'x'</span><span class="p">:</span> <span class="mi">1</span><span class="p">},</span> <span class="p">{</span><span class="s1">'$inc'</span><span class="p">:</span> <span class="p">{</span><span class="s1">'x'</span><span class="p">:</span> <span class="mi">3</span><span class="p">}},</span> <span class="n">upsert</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</pre></div>
<p>Mais sous <em>posgresql</em>, il n'existe pas de mot-clé <em>upsert</em>. Et c'est là que tu te demandes:</p>
<p><em>"Comment faire un upsert en postgresql, tout en évitant les problèmes d'accès concurrents ?"</em></p>
<p>Si tu n'es pas pressé, attends la sortie de postgresql 9.5 qui va inclure <a class="reference external" href="https://wiki.postgresql.org/wiki/What's_new_in_PostgreSQL_9.5#INSERT_..._ON_CONFLICT_DO_NOTHING.2FUPDATE_.28.22UPSERT.22.29">la syntaxe du upsert</a>.</p>
<p>En imaginant une table qui ressemble à ça:</p>
<div class="highlight"><pre><span></span><span class="k">SELECT</span><span class="w"> </span><span class="n">username</span><span class="p">,</span><span class="w"> </span><span class="n">logins</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">user_logins</span><span class="p">;</span><span class="w"></span>
<span class="n">username</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">logins</span><span class="w"></span>
<span class="c1">----------+--------</span>
<span class="n">James</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="mi">4</span><span class="w"></span>
<span class="n">Lois</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="mi">2</span><span class="w"></span>
<span class="p">(</span><span class="mi">2</span><span class="w"> </span><span class="k">rows</span><span class="p">)</span><span class="w"></span>
</pre></div>
<p>Et que tu veuilles ajouter deux nouveaux <em>logins</em>:</p>
<div class="highlight"><pre><span></span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">user_logins</span><span class="w"> </span><span class="p">(</span><span class="n">username</span><span class="p">,</span><span class="w"> </span><span class="n">logins</span><span class="p">)</span><span class="w"></span>
<span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">'Naomi'</span><span class="p">,</span><span class="mi">1</span><span class="p">),(</span><span class="s1">'James'</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span><span class="w"></span>
</pre></div>
<p>En temps normal, tu auras cette erreur si le <em>username</em> existe déjà en base:</p>
<div class="highlight"><pre><span></span><span class="n">ERROR</span><span class="p">:</span><span class="w"> </span><span class="n">duplicate</span><span class="w"> </span><span class="k">key</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="n">violates</span><span class="w"> </span><span class="k">unique</span><span class="w"> </span><span class="k">constraint</span><span class="w"> </span><span class="ss">"users_pkey"</span><span class="w"></span>
<span class="n">DETAIL</span><span class="p">:</span><span class="w"> </span><span class="k">Key</span><span class="w"> </span><span class="p">(</span><span class="n">username</span><span class="p">)</span><span class="o">=</span><span class="p">(</span><span class="n">James</span><span class="p">)</span><span class="w"> </span><span class="n">already</span><span class="w"> </span><span class="k">exists</span><span class="p">.</span><span class="w"></span>
</pre></div>
<p>Et bien en 9.5, tu pourras gérer ça de cette manière:</p>
<div class="highlight"><pre><span></span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">user_logins</span><span class="w"> </span><span class="p">(</span><span class="n">username</span><span class="p">,</span><span class="w"> </span><span class="n">logins</span><span class="p">)</span><span class="w"></span>
<span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="s1">'Naomi'</span><span class="p">,</span><span class="mi">1</span><span class="p">),(</span><span class="s1">'James'</span><span class="p">,</span><span class="mi">1</span><span class="p">)</span><span class="w"></span>
<span class="k">ON</span><span class="w"> </span><span class="n">CONFLICT</span><span class="w"> </span><span class="p">(</span><span class="n">username</span><span class="p">)</span><span class="w"></span>
<span class="k">DO</span><span class="w"> </span><span class="k">UPDATE</span><span class="w"> </span><span class="k">SET</span><span class="w"> </span><span class="n">logins</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">user_logins</span><span class="p">.</span><span class="n">logins</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">EXCLUDED</span><span class="p">.</span><span class="n">logins</span><span class="p">;</span><span class="w"></span>
</pre></div>
<p>Génial non ?</p>
<p><em>"Oui mais bon, de mon côté, en production, j'ai du postgresql 9.4 et c'est pas près de changer."</em></p>
<p>Dans ce cas-là, je t'invite à lire <a class="reference external" href="http://www.depesz.com/2012/06/10/why-is-upsert-so-complicated/">ce très bon article</a>
qui date un peu, mais qui résume très bien la situation.</p>
<p>Pour rester simple, l'idée est d'effectuer un <em>update</em> si l'entrée existe déjà ou un <em>insert</em> sinon.
Mais lors d'accès concurrents, il se peut très bien que l'entrée ait été ajoutée par un autre processus entre
ta tentative ratée d'<em>update</em> et ton <em>insert</em> qui suit. Et là, ça plante lamentablement.</p>
<p>Alors oui, il existe les <em>locks</em> et les transactions, mais çe n'est pas suffisant et ça peut poser certains problèmes.
Par exemple, <em>postgresql</em> stoppe une transaction en cours lorsqu'il rencontre une erreur.
Pour plus de détail, lis l'article que j'ai cité précédemment.</p>
<p>Du coup, la <a class="reference external" href="http://stackoverflow.com/questions/1109061/insert-on-duplicate-update-in-postgresql?answertab=votes#tab-top">solution admise par la communauté stackoverflow</a>
est la suivante. Tu écris une fonction <a class="reference external" href="https://fr.wikipedia.org/wiki/PL/SQL">pl/sql</a> qui boucle sur le <em>update</em> ou <em>insert</em>, en utilisant l'exception <em>unique_violation</em>.
Du coup, cette fonction s'appelle à l'aide d'un seul <em>select</em>, donc pas besoin de <em>lock</em> ou de transaction:</p>
<div class="highlight"><pre><span></span><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">db</span><span class="w"> </span><span class="p">(</span><span class="n">a</span><span class="w"> </span><span class="nb">INT</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">);</span><span class="w"></span>
<span class="k">CREATE</span><span class="w"> </span><span class="k">FUNCTION</span><span class="w"> </span><span class="n">merge_db</span><span class="p">(</span><span class="k">key</span><span class="w"> </span><span class="nb">INT</span><span class="p">,</span><span class="w"> </span><span class="k">data</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">)</span><span class="w"> </span><span class="k">RETURNS</span><span class="w"> </span><span class="n">VOID</span><span class="w"> </span><span class="k">AS</span><span class="w"></span>
<span class="err">$$</span><span class="w"></span>
<span class="k">BEGIN</span><span class="w"></span>
<span class="w"> </span><span class="n">LOOP</span><span class="w"></span>
<span class="w"> </span><span class="c1">-- first try to update the key</span>
<span class="w"> </span><span class="c1">-- note that "a" must be unique</span>
<span class="w"> </span><span class="k">UPDATE</span><span class="w"> </span><span class="n">db</span><span class="w"> </span><span class="k">SET</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">data</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">key</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">IF</span><span class="w"> </span><span class="k">found</span><span class="w"> </span><span class="k">THEN</span><span class="w"></span>
<span class="w"> </span><span class="k">RETURN</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">END</span><span class="w"> </span><span class="k">IF</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">-- not there, so try to insert the key</span>
<span class="w"> </span><span class="c1">-- if someone else inserts the same key concurrently,</span>
<span class="w"> </span><span class="c1">-- we could get a unique-key failure</span>
<span class="w"> </span><span class="k">BEGIN</span><span class="w"></span>
<span class="w"> </span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">db</span><span class="p">(</span><span class="n">a</span><span class="p">,</span><span class="n">b</span><span class="p">)</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="k">key</span><span class="p">,</span><span class="w"> </span><span class="k">data</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">RETURN</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">EXCEPTION</span><span class="w"> </span><span class="k">WHEN</span><span class="w"> </span><span class="n">unique_violation</span><span class="w"> </span><span class="k">THEN</span><span class="w"></span>
<span class="w"> </span><span class="c1">-- do nothing, and loop to try the UPDATE again</span>
<span class="w"> </span><span class="k">END</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">END</span><span class="w"> </span><span class="n">LOOP</span><span class="p">;</span><span class="w"></span>
<span class="k">END</span><span class="p">;</span><span class="w"></span>
<span class="err">$$</span><span class="w"></span>
<span class="k">LANGUAGE</span><span class="w"> </span><span class="n">plpgsql</span><span class="p">;</span><span class="w"></span>
<span class="k">SELECT</span><span class="w"> </span><span class="n">merge_db</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s1">'david'</span><span class="p">);</span><span class="w"></span>
<span class="k">SELECT</span><span class="w"> </span><span class="n">merge_db</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s1">'dennis'</span><span class="p">);</span><span class="w"></span>
</pre></div>
Introduction à Asyncio2015-11-15T00:00:00+01:002015-11-15T00:00:00+01:00Morgantag:dotmobo.github.io,2015-11-15:/introduction-asyncio.html<p class="first last">Introduction à Asyncio</p>
<img alt="Django" class="align-right" src="./images/python.png" />
<p>La librairie <a class="reference external" href="http://asyncio.org/">Asyncio</a>
a fait beaucoup parler d'elle dernièrement, au point d'être
intégrée dans la bibliothèque standard depuis la version 3.4 de Python.</p>
<p>C'est la réponse aux <a class="reference external" href="https://gobyexample.com/goroutines">goroutines</a>
de <a class="reference external" href="https://golang.org/">Go</a>, inscrivant ainsi Python dans la liste des
langages permettant la programmation asynchrone. Ce type de programmation permet
de ne pas bloquer son programme lors des opérations I/O qui peuvent durer un
certain temps et de réagir lors de la réception des informations au
lieu de les attendre. Ça permet ainsi d'optimiser et d'améliorer fortement les
performances de son code.</p>
<p>Je t'invite à te renseigner sur les différences entre programmation asynchrone,
parallèle et concurrente via <a class="reference external" href="http://sametmax.com/la-difference-entre-la-programmation-asynchrone-parallele-et-concurrente/">l'article de Sam&Max</a>
et <a class="reference external" href="https://www.youtube.com/watch?v=JpqnNCx7wVY">la vidéo de Jonathan Worthington</a> du monde Perl.</p>
<p>Asyncio utilise une boucle d'événements qui va contenir l'ensemble de nos tâches
à exécuter. Ces tâches devront être sous la forme de <a class="reference external" href="http://sametmax.com/quest-ce-quune-coroutine-en-python-et-a-quoi-ca-sert/">coroutines</a>,
qui sont des sortes de générateurs inversés, c'est-à-dire qu'on y envoie des données à la place
d'en reçevoir. C'est le côté <em>lazy</em> des coroutines qui permet à Asyncio de les
exécuter en asynchrone.</p>
<p>Trêve de blabla et passons à la pratique. Il y a déjà beaucoup d'articles sur le net
traitant du fonctionnement d'Asyncio et ce n'est pas forcément facile
de s'y retrouver. Tu vas donc voir ici un cas d'usage concret, qui est le développement
d'un aggrégateur de données <em>json</em> performant. Le tutorial sera en python 3.5,
ce qui te permettra d'utiliser les nouveaux mots clés <strong>async</strong> et <strong>await</strong>.</p>
<p>Tu utiliseras la boucle d'événements, les coroutines et les objets <strong>Future</strong>.
L'idée n'est pas de faire le code le plus simple et performant possible, mais plutôt de passer
en revue l'ensemble des concepts et mots-clés utiles.</p>
<p>Pour Asyncio, il n'y a rien à installer à part python 3.5. Par contre, il va te
falloir <a class="reference external" href="https://github.com/KeepSafe/aiohttp">aiohttp</a> pour faire les requêtes http:</p>
<div class="highlight"><pre><span></span>pip install aiohttp
</pre></div>
<p>Et c'est là où le bât blesse. Tu ne pourras pas utiliser <a class="reference external" href="http://docs.python-requests.org/en/latest/">requests</a> par exemple, car
il faut utiliser des outils compatibles avec Asyncio, c'est-à-dire écrits sous forme
de coroutines. Sinon, le programme bloquera la boucle d'événements et ça ne sera
pas asynchrone. Pareil pour les accès <em>BDD</em>, il faut utiliser <a class="reference external" href="https://github.com/aio-libs/aiopg">aiopg</a> pour postgresql par exemple.</p>
<p>Tu crées un fichier <em>asyncio35.py</em>, tu importes <em>aiohttp</em> et <em>asyncio</em> et tu déclares ta liste
d'urls:</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">aiohttp</span>
<span class="n">URLS</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'http://ip.jsontest.com/'</span><span class="p">,</span> <span class="s1">'http://headers.jsontest.com/'</span><span class="p">,</span>
<span class="s1">'http://date.jsontest.com/'</span><span class="p">]</span>
</pre></div>
<p>Tu vas alors créer ta coroutine qui va récupérer les données renvoyées par une
url et les insérer dans un objet <strong>Future</strong>:</p>
<div class="highlight"><pre><span></span><span class="k">async</span> <span class="k">def</span> <span class="nf">call_url</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">future</span><span class="p">):</span>
<span class="sd">""" Coroutine récupérant les données provenant d'une url """</span>
<span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">response</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
<span class="n">future</span><span class="o">.</span><span class="n">set_result</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
</pre></div>
<p>Plusieurs explications sont nécessaires ici:</p>
<ul class="simple">
<li><strong>async</strong>: Nouveau mot-clé introduit en python 3.5, à mettre avant le <strong>def</strong>, qui permet de spécifier que cette méthode est une coroutine asynchrone. Ça vient remplacer le <strong>@asyncio.coroutine</strong> de python 3.4.</li>
<li><strong>async with</strong>: Permet d'utiliser des <em>context managers</em> asynchrones.</li>
<li><strong>await</strong>: Bloque l'exécution de la coroutine jusqu'à la fin du traitement de l'instruction, ici <strong>response.json()</strong>. Ça vient remplacer le <strong>yield from</strong> de python 3.4.</li>
<li><strong>future.set_result</strong>: Définit la valeur de l'objet <strong>Future</strong>.</li>
</ul>
<p>Ensuite, dans ton <em>main</em>, tu initalises ta boucle, ton client <em>aiohttp</em>, ta liste
de tâches et ta liste de résultats:</p>
<div class="highlight"><pre><span></span><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
<span class="c1"># On initialise les variables</span>
<span class="n">list_results</span><span class="p">,</span> <span class="n">list_tasks</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">[]</span>
<span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_event_loop</span><span class="p">()</span>
<span class="n">client</span> <span class="o">=</span> <span class="n">aiohttp</span><span class="o">.</span><span class="n">ClientSession</span><span class="p">(</span><span class="n">loop</span><span class="o">=</span><span class="n">loop</span><span class="p">)</span>
</pre></div>
<p>Tu ajoutes ton <em>callback</em> pour les objets <strong>Future</strong>:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">fill_results_list</span><span class="p">(</span><span class="n">future</span><span class="p">):</span>
<span class="sd">""" Callback de l'objet future qui ajoute sa valeur dans une liste """</span>
<span class="n">list_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">())</span>
</pre></div>
<p>Pour chaque url, tu vas:</p>
<ul class="simple">
<li>créer un objet <strong>Future</strong>.</li>
<li>ajouter la méthode <strong>call_url</strong> à la liste des tâches à accomplir via la méthode <strong>ensure_future</strong>.</li>
<li>ajouter ton <em>callback</em> <strong>fill_results_list</strong> à ton objet <strong>Future</strong> via la méthode <strong>add_done_callback</strong>.</li>
</ul>
<div class="highlight"><pre><span></span><span class="c1"># On créé les objets Future et la liste des tâches</span>
<span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">URLS</span><span class="p">:</span>
<span class="n">future</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Future</span><span class="p">()</span>
<span class="n">list_tasks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">ensure_future</span><span class="p">(</span><span class="n">call_url</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">future</span><span class="p">)))</span>
<span class="n">future</span><span class="o">.</span><span class="n">add_done_callback</span><span class="p">(</span><span class="n">fill_results_list</span><span class="p">)</span>
</pre></div>
<p>Puis, il suffit de lancer l'exécution des tâches de manière asynchrone via
la boucle d'événements et sa méthode <strong>run_until_complete</strong>. Ton programme
sera bloqué ici jusqu'à la fin du traitement de toutes les tâches et donc de la
réception des objets <strong>Future</strong> via <strong>asyncio.wait</strong>. À la fin, il affiche la liste
des résultats sur la sortie standard:</p>
<div class="highlight"><pre><span></span><span class="c1"># Exécution des tâches</span>
<span class="n">loop</span><span class="o">.</span><span class="n">run_until_complete</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">wait</span><span class="p">(</span><span class="n">list_tasks</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="n">list_results</span><span class="p">)</span>
</pre></div>
<p>Enfin, tu peux fermer le client <em>aiohttp</em> et la boucle d'événements:</p>
<div class="highlight"><pre><span></span><span class="c1"># Ferme le client et la boucle</span>
<span class="n">client</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="n">loop</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</pre></div>
<p>Encore une chose concernant la boucle. Celle-ci est unique pour tout le programme.
Donc il faut faire attention quand tu la manipules à plusieurs endroits du code,
et quand tu la fermes.</p>
<p>Voici le résultat final :</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">asyncio</span>
<span class="kn">import</span> <span class="nn">aiohttp</span>
<span class="sd">"""</span>
<span class="sd">Aggrégation de données provenant d'urls</span>
<span class="sd">"""</span>
<span class="n">URLS</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'http://ip.jsontest.com/'</span><span class="p">,</span> <span class="s1">'http://headers.jsontest.com/'</span><span class="p">,</span>
<span class="s1">'http://date.jsontest.com/'</span><span class="p">]</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">call_url</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">future</span><span class="p">):</span>
<span class="sd">""" Coroutine récupérant les données provenant d'une url """</span>
<span class="k">async</span> <span class="k">with</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">as</span> <span class="n">response</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
<span class="n">future</span><span class="o">.</span><span class="n">set_result</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
<span class="c1"># On initialise les variables</span>
<span class="n">list_results</span><span class="p">,</span> <span class="n">list_tasks</span> <span class="o">=</span> <span class="p">[],</span> <span class="p">[]</span>
<span class="n">loop</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">get_event_loop</span><span class="p">()</span>
<span class="n">client</span> <span class="o">=</span> <span class="n">aiohttp</span><span class="o">.</span><span class="n">ClientSession</span><span class="p">(</span><span class="n">loop</span><span class="o">=</span><span class="n">loop</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">fill_results_list</span><span class="p">(</span><span class="n">future</span><span class="p">):</span>
<span class="sd">""" Callback de l'objet future qui ajoute sa valeur dans une liste """</span>
<span class="n">list_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">())</span>
<span class="c1"># On créé les objets Future et la liste des tâches</span>
<span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">URLS</span><span class="p">:</span>
<span class="n">future</span> <span class="o">=</span> <span class="n">asyncio</span><span class="o">.</span><span class="n">Future</span><span class="p">()</span>
<span class="n">list_tasks</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">ensure_future</span><span class="p">(</span><span class="n">call_url</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">future</span><span class="p">)))</span>
<span class="n">future</span><span class="o">.</span><span class="n">add_done_callback</span><span class="p">(</span><span class="n">fill_results_list</span><span class="p">)</span>
<span class="c1"># Exécution des tâches</span>
<span class="n">loop</span><span class="o">.</span><span class="n">run_until_complete</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">wait</span><span class="p">(</span><span class="n">list_tasks</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="n">list_results</span><span class="p">)</span>
<span class="c1"># Ferme le client et la boucle</span>
<span class="n">client</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="n">loop</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</pre></div>
<p>Et hop, tu exécutes tout ça:</p>
<div class="highlight"><pre><span></span>$ <span class="nb">time</span> python asyncio35.py
<span class="o">[{</span><span class="s1">'ip'</span>: <span class="s1">'109.221.53.120'</span><span class="o">}</span>,
<span class="o">{</span><span class="s1">'Host'</span>: <span class="s1">'headers.jsontest.com'</span>, <span class="s1">'User-Agent'</span>: <span class="s1">'Python/3.5 aiohttp/0.18.4'</span>, <span class="s1">'Accept'</span>: <span class="s1">'*/*'</span>, <span class="s1">'Content-Length'</span>: <span class="s1">'0'</span><span class="o">}</span>,
<span class="o">{</span><span class="s1">'date'</span>: <span class="s1">'11-14-2015'</span>, <span class="s1">'time'</span>: <span class="s1">'03:16:45 PM'</span>, <span class="s1">'milliseconds_since_epoch'</span>: <span class="m">1447514205836</span><span class="o">}]</span>
real 0m0.511s
user 0m0.263s
sys 0m0.033s
</pre></div>
<p><em>"Ok c'est sympa mais est-ce que c'est vraiment plus rapide en asynchrone ?"</em></p>
<p>Tu veux une preuve ? En voici une; le même programme sans Asyncio:</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">requests</span>
<span class="sd">"""</span>
<span class="sd">Aggrégation de données provenant d'urls</span>
<span class="sd">"""</span>
<span class="n">URLS</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'http://ip.jsontest.com/'</span><span class="p">,</span> <span class="s1">'http://headers.jsontest.com/'</span><span class="p">,</span>
<span class="s1">'http://date.jsontest.com/'</span><span class="p">]</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
<span class="n">list_results</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">URLS</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="n">list_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">json</span><span class="p">())</span>
<span class="nb">print</span><span class="p">(</span><span class="n">list_results</span><span class="p">)</span>
</pre></div>
<p>Tu l'exécutes:</p>
<div class="highlight"><pre><span></span>$ <span class="nb">time</span> python noasyncio35.py
<span class="o">[{</span><span class="s1">'ip'</span>: <span class="s1">'109.221.53.120'</span><span class="o">}</span>,
<span class="o">{</span><span class="s1">'Host'</span>: <span class="s1">'headers.jsontest.com'</span>, <span class="s1">'User-Agent'</span>: <span class="s1">'python-requests/2.8.1'</span>, <span class="s1">'Accept'</span>: <span class="s1">'*/*'</span><span class="o">}</span>,
<span class="o">{</span><span class="s1">'date'</span>: <span class="s1">'11-14-2015'</span>, <span class="s1">'time'</span>: <span class="s1">'11:57:03 AM'</span>, <span class="s1">'milliseconds_since_epoch'</span>: <span class="m">1447502223337</span><span class="o">}]</span>
real 0m1.188s
user 0m0.247s
sys 0m0.017s
</pre></div>
<p>Le double de temps ! Convaincu ?</p>
<p>Alors évidemment, ce n'est qu'un simple cas d'usage. Il y a beaucoup, mais
vraiment beaucoup plus à voir dans <a class="reference external" href="https://docs.python.org/3/library/asyncio.html">la doc officielle</a>.</p>
TinyDB, la base de données pure python2015-11-06T00:00:00+01:002015-11-06T00:00:00+01:00Morgantag:dotmobo.github.io,2015-11-06:/tinydb.html<p class="first last">TinyDB, la base de données pure python</p>
<img alt="TinyDB" class="align-right" src="./images/tinydb.png" />
<p><a class="reference external" href="https://github.com/msiemens/tinydb">TinyDB</a>, ce n'est pas la base de données qui va tout révolutionner, mais
c'est le petit outil sympa à avoir à portée de main.</p>
<p>Elle est orientée <em>document</em>, comme <a class="reference external" href="https://www.mongodb.org/">MongoDB</a>, en se basant sur des fichiers <a class="reference external" href="http://www.json.org/">JSON</a>.</p>
<p>Le code est écrit en pure python, sans besoin d'aucune dépendance, et est compatible
python 2 et 3.</p>
<p>Niveau utilisation, il ne faut pas espérer des perfs de malade ; ce n'est pas fait
pour ça.</p>
<p>Par contre, si tu as besoin d'une mini BDD pour afficher des news sur
un site, ça fera l'affaire.</p>
<p>Personnellement, je l'utilise plutôt lors de la rédaction de tests unitaires,
lorsque j'ai besoin d'une batterie de données de test.</p>
<p>Pour l'utiliser, tu l'installes via <em>pip</em>:</p>
<div class="highlight"><pre><span></span>pip install tinydb
</pre></div>
<p>Tu crées, par exemple, une base de données contenant des légumes:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">tinydb</span> <span class="kn">import</span> <span class="n">TinyDB</span>
<span class="o">>>></span> <span class="n">db</span> <span class="o">=</span> <span class="n">TinyDB</span><span class="p">(</span><span class="s1">'meslegumes.json'</span><span class="p">)</span>
</pre></div>
<p>Le fichier <em>meslegumes.json</em> correspond à ta base de données et s'est créé dans
le répertoire courant.</p>
<p>Tu crées alors une table <em>legumes</em>. Évite les caractères spéciaux dans le nom de la table,
ça peut poser problème:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">table</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">table</span><span class="p">(</span><span class="s1">'legumes'</span><span class="p">)</span>
</pre></div>
<p>Tu vas maintenant insérer des légumes via la méthode <em>insert</em>:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">table</span><span class="o">.</span><span class="n">insert</span><span class="p">({</span><span class="s1">'type'</span><span class="p">:</span> <span class="s1">'carotte'</span><span class="p">,</span> <span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">5</span><span class="p">})</span>
<span class="o">>>></span> <span class="n">table</span><span class="o">.</span><span class="n">insert</span><span class="p">({</span><span class="s1">'type'</span><span class="p">:</span> <span class="s1">'patate'</span><span class="p">,</span> <span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">6</span><span class="p">})</span>
<span class="o">>>></span> <span class="n">table</span><span class="o">.</span><span class="n">insert</span><span class="p">({</span><span class="s1">'type'</span><span class="p">:</span> <span class="s1">'navet'</span><span class="p">,</span> <span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">2</span><span class="p">})</span>
</pre></div>
<p>En affichant tous les éléments de ta table, tu verras tes légumes:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">table</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="p">[{</span><span class="sa">u</span><span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="sa">u</span><span class="s1">'type'</span><span class="p">:</span> <span class="sa">u</span><span class="s1">'carotte'</span><span class="p">},</span>
<span class="p">{</span><span class="sa">u</span><span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span> <span class="sa">u</span><span class="s1">'type'</span><span class="p">:</span> <span class="sa">u</span><span class="s1">'patate'</span><span class="p">},</span>
<span class="p">{</span><span class="sa">u</span><span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="sa">u</span><span class="s1">'type'</span><span class="p">:</span> <span class="sa">u</span><span class="s1">'navet'</span><span class="p">}]</span>
</pre></div>
<p>Tu peux désormais utiliser un langage de requête.
Par exemple, si tu veux récupérer toutes les carottes:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">tinydb</span> <span class="kn">import</span> <span class="n">where</span>
<span class="o">>>></span> <span class="n">table</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">where</span><span class="p">(</span><span class="s1">'type'</span><span class="p">)</span> <span class="o">==</span> <span class="s1">'carotte'</span><span class="p">)</span>
<span class="p">[{</span><span class="sa">u</span><span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="sa">u</span><span class="s1">'type'</span><span class="p">:</span> <span class="sa">u</span><span class="s1">'carotte'</span><span class="p">}]</span>
</pre></div>
<p>Ou tous les légumes qui sont plus de deux:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">table</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">where</span><span class="p">(</span><span class="s1">'nombre'</span><span class="p">)</span> <span class="o">></span> <span class="mi">2</span><span class="p">)</span>
<span class="p">[{</span><span class="sa">u</span><span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="sa">u</span><span class="s1">'type'</span><span class="p">:</span> <span class="sa">u</span><span class="s1">'carotte'</span><span class="p">},</span> <span class="p">{</span><span class="sa">u</span><span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span> <span class="sa">u</span><span class="s1">'type'</span><span class="p">:</span> <span class="sa">u</span><span class="s1">'patate'</span><span class="p">}]</span>
</pre></div>
<p>Tu peux utiliser le <strong>ET</strong> et le <strong>OU</strong> logique, pour combiner tes requêtes lors
de ta recherche de légumes:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">table</span><span class="o">.</span><span class="n">search</span><span class="p">((</span><span class="n">where</span><span class="p">(</span><span class="s1">'nombre'</span><span class="p">)</span> <span class="o">></span> <span class="mi">2</span><span class="p">)</span> <span class="o">&</span> <span class="p">(</span><span class="n">where</span><span class="p">(</span><span class="s1">'nombre'</span><span class="p">)</span> <span class="o"><</span> <span class="mi">6</span><span class="p">))</span>
<span class="p">[{</span><span class="sa">u</span><span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="sa">u</span><span class="s1">'type'</span><span class="p">:</span> <span class="sa">u</span><span class="s1">'carotte'</span><span class="p">}]</span>
<span class="o">>>></span> <span class="n">table</span><span class="o">.</span><span class="n">search</span><span class="p">((</span><span class="n">where</span><span class="p">(</span><span class="s1">'nombre'</span><span class="p">)</span> <span class="o"><</span> <span class="mi">3</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">where</span><span class="p">(</span><span class="s1">'nombre'</span><span class="p">)</span> <span class="o">></span> <span class="mi">5</span><span class="p">))</span>
<span class="p">[{</span><span class="sa">u</span><span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span> <span class="sa">u</span><span class="s1">'type'</span><span class="p">:</span> <span class="sa">u</span><span class="s1">'patate'</span><span class="p">},</span> <span class="p">{</span><span class="sa">u</span><span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="sa">u</span><span class="s1">'type'</span><span class="p">:</span> <span class="sa">u</span><span class="s1">'navet'</span><span class="p">}]</span>
</pre></div>
<p>Tu peux évidemment mettre à jour tes navets:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">table</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">10</span><span class="p">},</span> <span class="n">where</span><span class="p">(</span><span class="s1">'type'</span><span class="p">)</span> <span class="o">==</span> <span class="s1">'navet'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">table</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">where</span><span class="p">(</span><span class="s1">'type'</span><span class="p">)</span> <span class="o">==</span> <span class="s1">'navet'</span><span class="p">)</span>
<span class="p">[{</span><span class="sa">u</span><span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="sa">u</span><span class="s1">'type'</span><span class="p">:</span> <span class="sa">u</span><span class="s1">'navet'</span><span class="p">}]</span>
</pre></div>
<p>Et supprimer tes carottes:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">db</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">where</span><span class="p">(</span><span class="s1">'type'</span><span class="p">)</span> <span class="o">==</span> <span class="s1">'carotte'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">table</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="p">[{</span><span class="sa">u</span><span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span> <span class="sa">u</span><span class="s1">'type'</span><span class="p">:</span> <span class="sa">u</span><span class="s1">'patate'</span><span class="p">},</span> <span class="p">{</span><span class="sa">u</span><span class="s1">'nombre'</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="sa">u</span><span class="s1">'type'</span><span class="p">:</span> <span class="sa">u</span><span class="s1">'navet'</span><span class="p">}]</span>
</pre></div>
<p>Ou carrément vider tous tes légumes:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">table</span><span class="o">.</span><span class="n">purge</span><span class="p">()</span>
<span class="o">>>></span> <span class="n">table</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="p">[]</span>
</pre></div>
<p>Voilà pour les bases! Pour un usage simple, ça devrait te suffire.</p>
<p>Il existe <a class="reference external" href="http://tinydb.readthedocs.org/en/latest/usage.html#recap">d'autres opérations</a>
pour le langage de requête comme <em>insert_multiple</em>,
<em>delete</em>, <em>increment</em>, <em>decrement</em>, <em>get</em>, <em>contains</em>, <em>count</em>.</p>
<p>Tu peux également stocker les légumes en mémoire à la place du fichier json:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">tinydb.storages</span> <span class="kn">import</span> <span class="n">MemoryStorage</span>
<span class="o">>>></span> <span class="n">db</span> <span class="o">=</span> <span class="n">TinyDB</span><span class="p">(</span><span class="n">storage</span><span class="o">=</span><span class="n">MemoryStorage</span><span class="p">)</span>
</pre></div>
<p>Et utiliser des <em>middlewares</em>, pour faire du cache par exemple:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">tinydb.storages</span> <span class="kn">import</span> <span class="n">JSONStorage</span>
<span class="o">>>></span> <span class="kn">from</span> <span class="nn">tinydb.middlewares</span> <span class="kn">import</span> <span class="n">CachingMiddleware</span>
<span class="o">>>></span> <span class="n">db</span> <span class="o">=</span> <span class="n">TinyDB</span><span class="p">(</span><span class="s1">'meslegumes.json'</span><span class="p">,</span> <span class="n">storage</span><span class="o">=</span><span class="n">CachingMiddleware</span><span class="p">(</span><span class="n">JSONStorage</span><span class="p">))</span>
</pre></div>
<p>Enfin, il est également possible de customiser TinyDB en écrivant ton propre
<a class="reference external" href="http://tinydb.readthedocs.org/en/latest/extend.html#write-a-serializer">Serializer</a>,
ou d'écrire une <a class="reference external" href="http://tinydb.readthedocs.org/en/latest/extend.html#write-a-custom-storage">implémentation YAML</a>
pour le stockage des données à la place de JSON, ou encore d'écrire
<a class="reference external" href="http://tinydb.readthedocs.org/en/latest/extend.html#write-a-custom-middleware">tes propres Middlewares</a>.</p>
Gestion des pays dans django avec django-countries2015-10-31T00:00:00+01:002015-10-31T00:00:00+01:00Morgantag:dotmobo.github.io,2015-10-31:/django-countries.html<p class="first last">Gestion des pays dans django avec django-countries</p>
<img alt="Django" class="align-right" src="./images/djangopony.png" />
<p>En développement web, on a souvent besoin de faire des formulaires. Et
notamment des formulaires d'inscription, avec saisie du nom, prénom, adresse et
donc pays.</p>
<p>Tu m'accorderas que laisser le champs pays libre risque de générer des
erreurs de saisie, et que gérer manuellement une liste déroulante de pays est
ennuyeux à mourir.</p>
<p>Rassure-toi, une solution existe, et c'est là qu'intervient
<a class="reference external" href="https://github.com/SmileyChris/django-countries">django-countries</a>.</p>
<p>Django-countries, c'est la librairie qui ne paie pas de mine mais qui, l'air de
rien, est vachement pratique. Elle propose:</p>
<ul class="simple">
<li>Une liste déroulante pour les formulaires, contenant la liste de tous les
pays avec leurs <a class="reference external" href="http://www.iso.org/iso/fr/country_codes.htm">codes iso 3166</a>,
traduite dans 25 langues.</li>
<li>Des widgets.</li>
<li>Des fichiers statiques correspondants aux icônes des drapeaux des pays.</li>
<li>Un champ pays pour les modèles.</li>
<li>Une compatibilité avec <a class="reference external" href="http://www.django-rest-framework.org/">Django Rest Framework</a>.</li>
</ul>
<p>Tu l'installes via pip:</p>
<div class="highlight"><pre><span></span>pip install django-countries
</pre></div>
<p>Et tu ajoutes <strong>django_countries</strong> dans la liste de tes <strong>INSTALLED_APPS</strong>
dans le fichier <em>settings.py</em> de django.</p>
<div class="highlight"><pre><span></span><span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">(</span><span class="o">...</span><span class="p">,</span>
<span class="n">django_countries</span>
<span class="p">)</span>
</pre></div>
<p>Au niveau de ton modèle, tu peux maintenant utiliser le champ <strong>CountryField</strong>,
qui est basé sur <strong>CharField</strong> et qui correspond au code iso 3166:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>
<span class="kn">from</span> <span class="nn">django_countries.fields</span> <span class="kn">import</span> <span class="n">CountryField</span>
<span class="k">class</span> <span class="nc">Person</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
<span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
<span class="n">country</span> <span class="o">=</span> <span class="n">CountryField</span><span class="p">(</span><span class="n">blank_label</span><span class="o">=</span><span class="s1">'(select country)'</span><span class="p">)</span>
</pre></div>
<p>Tu peux maintenant créer une personne avec un pays associé, et accéder aux
différents attributs du pays comme son code, son icône, son nom et autres:</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="n">person</span> <span class="o">=</span> <span class="n">Person</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Chris'</span><span class="p">,</span> <span class="n">country</span><span class="o">=</span><span class="s1">'NZ'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">person</span><span class="o">.</span><span class="n">country</span>
<span class="n">Country</span><span class="p">(</span><span class="n">code</span><span class="o">=</span><span class="s1">'NZ'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">person</span><span class="o">.</span><span class="n">country</span><span class="o">.</span><span class="n">name</span>
<span class="s1">'New Zealand'</span>
<span class="o">>>></span> <span class="n">person</span><span class="o">.</span><span class="n">country</span><span class="o">.</span><span class="n">flag</span>
<span class="s1">'/static/flags/nz.gif'</span>
<span class="o">>>></span> <span class="n">person</span><span class="o">.</span><span class="n">country</span><span class="o">.</span><span class="n">alpha3</span>
<span class="s1">'NZL'</span>
<span class="o">>>></span> <span class="n">person</span><span class="o">.</span><span class="n">country</span><span class="o">.</span><span class="n">numeric</span>
<span class="s1">'554'</span>
<span class="o">>>></span> <span class="n">person</span><span class="o">.</span><span class="n">country</span><span class="o">.</span><span class="n">numeric_padded</span>
<span class="s1">'554'</span>
</pre></div>
<p>Dans le formulaire associé à ton modèle, tu peux aussi utiliser un widget qui va
afficher le drapeau du pays à côté de la liste déroulante:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django_countries.widgets</span> <span class="kn">import</span> <span class="n">CountrySelectWidget</span>
<span class="k">class</span> <span class="nc">PersonForm</span><span class="p">(</span><span class="n">forms</span><span class="o">.</span><span class="n">ModelForm</span><span class="p">):</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">Person</span>
<span class="n">fields</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'name'</span><span class="p">,</span> <span class="s1">'country'</span><span class="p">)</span>
<span class="n">widgets</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'country'</span><span class="p">:</span> <span class="n">CountrySelectWidget</span><span class="p">()}</span>
</pre></div>
<p>Dans le <em>settings.py</em>, on peut spécifier les pays à utiliser:</p>
<div class="highlight"><pre><span></span><span class="n">COUNTRIES_ONLY</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'NZ'</span><span class="p">,</span> <span class="s1">'AU'</span><span class="p">]</span>
</pre></div>
<p>Ou carrément customiser la liste:</p>
<div class="highlight"><pre><span></span><span class="n">COUNTRIES_OVERRIDE</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'NZ'</span><span class="p">:</span> <span class="n">_</span><span class="p">(</span><span class="s1">'Middle Earth'</span><span class="p">),</span>
<span class="s1">'AU'</span><span class="p">:</span> <span class="kc">None</span>
<span class="p">}</span>
</pre></div>
<p>Et on a donc également une compatibilité avec Django Rest Framework en modifiant
le serializer de cette manière:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">PersonSerializer</span><span class="p">(</span><span class="n">serializers</span><span class="o">.</span><span class="n">ModelSerializer</span><span class="p">):</span>
<span class="n">country</span> <span class="o">=</span> <span class="n">CountryField</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">Person</span>
<span class="n">fields</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'name'</span><span class="p">,</span> <span class="s1">'email'</span><span class="p">,</span> <span class="s1">'country'</span><span class="p">)</span>
</pre></div>
<p>Je ne suis pas rentré dans le détail de la customisation pour rester simple,
mais il est possible de modifier pas mal de choses au niveau de la liste,
du champ, de l'affichage et du paramétrage.</p>
<p>A voir dans la doc officielle si ça t'intéresse!</p>
Intégration continue sous Github avec Tox, Travis, Coveralls et Landscape2015-10-25T00:00:00+02:002015-10-25T00:00:00+02:00Morgantag:dotmobo.github.io,2015-10-25:/integration-continue.html<p class="first last">Intégration continue sous Github avec Tox, Travis, Coveralls et Landscape</p>
<img alt="Travis" class="align-right" src="./images/travis.png" />
<p>L'<a class="reference external" href="https://fr.wikipedia.org/wiki/Int%C3%A9gration_continue">intégration continue</a>
est une part très importante d'un projet, à ne surtout pas négliger.
Elle permet de vérifier constamment son code via des tests unitaires,
généralement à chaque <em>commit</em>. Ceci permet de tester la qualité et la robustesse
du code et d'éviter toutes régressions.</p>
<p>Dans cet article, tu verras comment mettre en place l'intégration continue d'une application
<a class="reference external" href="https://www.python.org/">python</a> sous <a class="reference external" href="https://github.com/">Github</a>. À la fin de cette lecture, tu seras capable:</p>
<ul class="simple">
<li>d'écrire un test unitaire simple.</li>
<li>de vérifier la couverture de ton code.</li>
<li>d'exécuter tes tests sous plusieurs versions de python.</li>
<li>d'automatiser l’exécution des tests à chaque <em>commit</em>.</li>
<li>d'automatiser la vérification de la couverture du code.</li>
<li>d'automatiser l'analyse de la qualité de ton code.</li>
<li>d'utiliser les badges github pour l'affichage des rapports.</li>
</ul>
<p>L'application de démo de ce tuto est dispo <a class="reference external" href="https://github.com/dotmobo/demo-integration-continue">ici</a>.</p>
<div class="section" id="creer-une-application">
<h2>1) Créer une application</h2>
<p>Dans le répertoire de ton projet <em>myproject</em>, tu vas créer un package <em>myapp</em> contenant le
classique <em>__init__.py</em>, ainsi qu'un fichier <em>maths.py</em>.
Celui-ci contiendra une méthode d'addition et de soustraction:</p>
<div class="highlight"><pre><span></span><span class="c1"># -*- coding: utf-8 -*-</span>
<span class="sd">"""</span>
<span class="sd">Provides an addition function and a subtraction function</span>
<span class="sd">"""</span>
<span class="k">def</span> <span class="nf">addition</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
<span class="sd">""" Méthode d'addition """</span>
<span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span>
<span class="k">def</span> <span class="nf">subtraction</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
<span class="sd">""" Méthode de soustraction """</span>
<span class="k">return</span> <span class="n">a</span> <span class="o">-</span> <span class="n">b</span>
</pre></div>
<p>Tu vas donc mettre en place l'intégration continue sur ce petit bout de code.</p>
<p>Tu ajoutes également dans le répertoire du projet le fichier <em>setup.py</em> suivant:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="kn">from</span> <span class="nn">setuptools</span> <span class="kn">import</span> <span class="n">setup</span>
<span class="n">setup</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'myproject'</span><span class="p">,</span>
<span class="n">version</span><span class="o">=</span><span class="s1">'1.0'</span><span class="p">,</span>
<span class="n">description</span><span class="o">=</span><span class="s1">'My project'</span><span class="p">,</span>
<span class="n">author</span><span class="o">=</span><span class="s1">'Me'</span><span class="p">,</span>
<span class="n">author_email</span><span class="o">=</span><span class="s1">'me@noreply.com'</span><span class="p">,</span>
<span class="n">url</span><span class="o">=</span><span class="s1">'http://myproject'</span><span class="p">,</span>
<span class="n">packages</span><span class="o">=</span><span class="p">[],</span>
<span class="p">)</span>
</pre></div>
<p>Puis, tu exécutes la commande suivante afin d'ajouter ton application dans le <em>path</em>
python:</p>
<div class="highlight"><pre><span></span><span class="n">python</span> <span class="n">setup</span><span class="o">.</span><span class="n">py</span> <span class="n">develop</span>
</pre></div>
</div>
<div class="section" id="ajouter-des-tests-unitaires">
<h2>2) Ajouter des tests unitaires</h2>
<p>Tu vas maintenant créer des tests unitaires à l'aide de
<a class="reference external" href="https://docs.python.org/3/library/unittest.html">unittest</a>.</p>
<p>Au même niveau que ton package <em>myapp</em>, tu vas créer un package <em>tests</em> qui sera
dédié aux tests unitaires. Celui-ci doit contenir le fichier <em>__init__.py</em> et le
fichier <em>test_maths.py</em> suivant:</p>
<div class="highlight"><pre><span></span><span class="c1"># -*- coding: utf-8 -*-</span>
<span class="sd">"""</span>
<span class="sd">Tests unitaires</span>
<span class="sd">"""</span>
<span class="kn">from</span> <span class="nn">unittest</span> <span class="kn">import</span> <span class="n">TestCase</span><span class="p">,</span> <span class="n">main</span>
<span class="kn">from</span> <span class="nn">myapp.maths</span> <span class="kn">import</span> <span class="n">addition</span><span class="p">,</span> <span class="n">subtraction</span>
<span class="k">class</span> <span class="nc">MathsTest</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Classe qui va contenir nos test unitaires</span>
<span class="sd"> """</span>
<span class="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">""" Méthode qui permet d'initialiser des variables pour nos tests """</span>
<span class="bp">self</span><span class="o">.</span><span class="n">a</span> <span class="o">=</span> <span class="mi">25</span>
<span class="bp">self</span><span class="o">.</span><span class="n">b</span> <span class="o">=</span> <span class="mi">12</span>
<span class="k">def</span> <span class="nf">test_addition</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">""" Test de l'addition """</span>
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">addition</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">a</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">b</span><span class="p">),</span> <span class="mi">37</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">test_subtraction</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">""" Test de la soustraction """</span>
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">subtraction</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">a</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">b</span><span class="p">),</span> <span class="mi">13</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">tearDown</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">""" Méthode appelée à la fin des tests """</span>
<span class="bp">self</span><span class="o">.</span><span class="n">a</span> <span class="o">=</span> <span class="kc">None</span>
<span class="bp">self</span><span class="o">.</span><span class="n">b</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<p>Et pour vérifier le bon fonctionnement de tes tests, tu peux les exécuter via:</p>
<div class="highlight"><pre><span></span>python tests/test_maths.py
</pre></div>
<p>Pour plus d'informations concernant les tests unitaires, je t'invite à te pencher
sur le dossier de Sam&Max :</p>
<ul class="simple">
<li><a class="reference external" href="http://sametmax.com/un-gros-guide-bien-gras-sur-les-tests-unitaires-en-python-partie-1/">http://sametmax.com/un-gros-guide-bien-gras-sur-les-tests-unitaires-en-python-partie-1/</a></li>
<li><a class="reference external" href="http://sametmax.com/un-gros-guide-bien-gras-sur-les-tests-unitaires-en-python-partie-2/">http://sametmax.com/un-gros-guide-bien-gras-sur-les-tests-unitaires-en-python-partie-2/</a></li>
<li><a class="reference external" href="http://sametmax.com/un-gros-guide-bien-gras-sur-les-tests-unitaires-en-python-partie-3/">http://sametmax.com/un-gros-guide-bien-gras-sur-les-tests-unitaires-en-python-partie-3/</a></li>
<li><a class="reference external" href="http://sametmax.com/un-gros-guide-bien-gras-sur-les-tests-unitaires-en-python-partie-4/">http://sametmax.com/un-gros-guide-bien-gras-sur-les-tests-unitaires-en-python-partie-4/</a></li>
</ul>
</div>
<div class="section" id="couvrir-son-code-avec-coverage">
<h2>3) Couvrir son code avec Coverage</h2>
<p>Tu vas maintenant ajouter les utilitaires permettant la couverture de ton code.</p>
<p>Premièrement, tu installes <a class="reference external" href="https://bitbucket.org/ned/coveragepy">coverage</a>:</p>
<div class="highlight"><pre><span></span>pip install coverage
</pre></div>
<p>Puis, tu crées le fichier de configuration de coverage appelé <em>.coveragerc</em>
dans ton répertoire <em>myproject</em>:</p>
<div class="highlight"><pre><span></span><span class="o">[</span>run<span class="o">]</span>
<span class="nb">source</span> <span class="o">=</span>
myapp
<span class="o">[</span>report<span class="o">]</span>
<span class="nv">omit</span> <span class="o">=</span> */tests/*
</pre></div>
<p>Tu y indiques d'exéctuer les tests de ton application <em>myapp</em>, tout en
ignorant d'analyser la couverture des fichiers de tests.
Sinon, il faudrait faire des tests unitaires pour tester les tests unitaires !</p>
<p>Tu lances les tests unitaires avec coverage:</p>
<div class="highlight"><pre><span></span>coverage run -m unittest discover tests/
</pre></div>
<p>Tu peux désormais afficher un rapport simple via:</p>
<div class="highlight"><pre><span></span>coverage report
</pre></div>
<p>Ou un rapport html via:</p>
<div class="highlight"><pre><span></span>coverage html
</pre></div>
<p>Celui-ci s'est créé dans le répertoire <em>htmlcov</em>. A l'aide de ce rapport, tu
vas pouvoir visualiser le pourcentage de code couvert ainsi que les zones de code
couvertes et non couvertes, fichier par fichier. Plutôt pratique non ?</p>
</div>
<div class="section" id="utiliser-tox-pour-l-execution-des-tests">
<h2>4) Utiliser Tox pour l'exécution des tests</h2>
<p><a class="reference external" href="https://testrun.org/tox/latest/">Tox</a> vise à standardiser l'exécution des tests
unitaires en python. Il permet, à l'aide d'environnements virtuels, de tester ton
code sous plusieurs interpréteurs python et sous plusieurs versions de librairies.</p>
<p>Il est très simple d'utilisation et s’interface parfaitement avec Travis.</p>
<p>Tu peux l'installer via pip:</p>
<div class="highlight"><pre><span></span>pip install tox
</pre></div>
<p>Ensuite, il te faut créer le fichier <em>tox.ini</em> dans le répertoire <em>myproject</em>:</p>
<div class="highlight"><pre><span></span><span class="p">[</span><span class="n">tox</span><span class="p">]</span>
<span class="n">envlist</span><span class="o">=</span><span class="n">py27</span><span class="p">,</span><span class="n">py34</span>
<span class="p">[</span><span class="n">testenv</span><span class="p">]</span>
<span class="n">deps</span><span class="o">=</span><span class="n">coverage</span>
<span class="n">commands</span><span class="o">=</span><span class="n">coverage</span> <span class="n">run</span> <span class="o">-</span><span class="n">m</span> <span class="n">unittest</span> <span class="n">discover</span> <span class="n">tests</span><span class="o">/</span>
</pre></div>
<p>Explication:</p>
<ul class="simple">
<li><em>envlist</em> permet de lister les interpréteurs python que l'on veut tester. Ici,
tu vas tester ton application sous python 2.7 et python 3.4. Il faut évidemment
les installer sur ton système si ce n'est pas déjà fait.</li>
<li><em>deps</em> liste les dépendances à installer dans le virtualenv qui sera créé.</li>
<li><em>commands</em> indique la commande à exécuter pour lancer les tests unitaires.</li>
<li>il y a plein d'autres paramètres utilisables, va voir dans la
<a class="reference external" href="https://testrun.org/tox/latest/example/basic.html">doc officielle</a>.</li>
</ul>
<p>Enfin, pour exécuter tes tests sous les différents environnements, lance la
commande:</p>
<div class="highlight"><pre><span></span>tox
</pre></div>
<p>Plutôt simple non ?</p>
<p>Crée-toi un dépôt sur Github et <em>commit</em> tout ça.</p>
</div>
<div class="section" id="activer-l-integration-continue-de-notre-projet-sous-travis-et-coveralls">
<h2>5) Activer l'intégration continue de notre projet sous Travis et Coveralls</h2>
<p><a class="reference external" href="https://travis-ci.org/">Travis</a> est un outil d'intégration continue, à la
manière de <a class="reference external" href="https://jenkins-ci.org/">Jenkins</a>. C'est lui qui va exécuter tes
tests unitaires à chaque <em>commit</em>, et qui va t'envoyer un mail si un problème a
été rencontré.</p>
<p>Tu peux t'y connecter via ton compte Github et y ajouter ton dépôt git via le bouton <em>+</em>.</p>
<p>Au préalable, il faut créer un fichier <em>.travis.yml</em> dans ton répertoire <em>myproject</em>:</p>
<div class="highlight"><pre><span></span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">python</span><span class="w"></span>
<span class="nt">python</span><span class="p">:</span><span class="w"> </span><span class="s">"2.7"</span><span class="w"></span>
<span class="nt">env</span><span class="p">:</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">TOX_ENV=py27</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">TOX_ENV=py34</span><span class="w"></span>
<span class="nt">install</span><span class="p">:</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">pip install tox</span><span class="w"></span>
<span class="nt">script</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">tox -e $TOX_ENV</span><span class="w"></span>
<span class="nt">after_success</span><span class="p">:</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">pip install coveralls</span><span class="w"></span>
<span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">coveralls</span><span class="w"></span>
</pre></div>
<p>On y indique les environnements de tox à tester et le script tox à exécuter.</p>
<p>Tu peux maintenant <em>commiter</em> tout ça sur ton dépôt Github, et te rendre sur le site
de travis pour visualiser les rapports d'exécution de tes tests!</p>
<p><em>"Attends un peu, c'est quoi la partie qui est dans le after_success, coveralls?"</em></p>
<p>Bien vu! <a class="reference external" href="https://coveralls.io/">Coveralls</a> est un outil qui permet de tester
la couverture de code à chaque <em>commit</em>.</p>
<p>Connecte-toi sur leur plate-forme via ton compte Github et active ton dépôt git via le bouton
<em>add repos</em>.</p>
<p>Tu vas ainsi pouvoir voir l'évolution de la couverture de code et analyser les rapports proposés.</p>
</div>
<div class="section" id="inspecter-la-qualite-du-code-avec-landscape-io">
<h2>6) Inspecter la qualité du code avec Landscape.io</h2>
<p>Landscape.io est une plate-forme qui va inspecter la qualité de ton code à chaque <em>commit</em>.
Celle-ci est gratuite pour les projets open-source disponibles sur Github.</p>
<p>Elle se base sur <a class="reference external" href="https://flake8.readthedocs.org/en/2.4.1/">flake8</a> comme outil
d'inspection de code.</p>
<p>Connecte-toi sur la plate-forme avec ton compte Github et ajoutes-y ton dépôt git
via <em>Add repository</em>.</p>
<p>Tu devras peut-être refaire un <em>commit</em> pour activer le bazar.</p>
</div>
<div class="section" id="ajouter-des-badges-sur-github">
<h2>7) Ajouter des badges sur github</h2>
<p>Tu va pouvoir te créer un fichier <em>README.rst</em> et y ajouter les badges <em>travis</em>,
<em>coveralls</em> et <em>landscape</em>. Tu peux trouver ces badges sous différents formats, notamment en
<a class="reference external" href="http://sphinx-doc.org/rest.html">restructuredText</a>, dans la configuration de
ton projet sur ces trois plate-formes.</p>
<p>Exemple:</p>
<div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">demo-integration-continue</span><span class="w"></span>
<span class="l l-Scalar l-Scalar-Plain">-------------------------</span><span class="w"></span>
<span class="l l-Scalar l-Scalar-Plain">Application de démo d'intégration continue sous github</span><span class="w"></span>
<span class="l l-Scalar l-Scalar-Plain">.. image:</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">https://travis-ci.org/dotmobo/demo-integration-continue.svg</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">:target</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">https://travis-ci.org/dotmobo/demo-integration-continue</span><span class="w"></span>
<span class="l l-Scalar l-Scalar-Plain">.. image:</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">https://coveralls.io/repos/dotmobo/demo-integration-continue/badge.svg?branch=master&service=github</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">:target</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">https://coveralls.io/github/dotmobo/demo-integration-continue?branch=master</span><span class="w"></span>
<span class="l l-Scalar l-Scalar-Plain">.. image:</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">https://landscape.io/github/dotmobo/demo-integration-continue/master/landscape.svg?style=flat</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">:target</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">https://landscape.io/github/dotmobo/demo-integration-continue/master</span><span class="w"></span>
<span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">:alt</span><span class="p p-Indicator">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">Code Health</span><span class="w"></span>
</pre></div>
<p><em>Commit</em> et rends-toi sur ton dépôt github pour voir
<a class="reference external" href="https://github.com/dotmobo/demo-integration-continue">le résultat</a>!</p>
</div>
Passer de Sublime Text à Atom pour développer en Python2015-10-17T00:00:00+02:002015-10-17T00:00:00+02:00Morgantag:dotmobo.github.io,2015-10-17:/sublime-text-to-atom.html<p class="first last">Passer de Sublime Text à Atom pour développer en Python</p>
<img alt="Pelican" class="align-right" src="./images/atom.png" />
<div class="line-block">
<div class="line"><em>"Sublime Text, c'est vraiment pas mal mais bon, c'est pas libre."</em></div>
<div class="line"><em>"Eclipse, c'est lourd, trop lourd."</em></div>
<div class="line"><em>"Vim, j'ai pas le temps de m'y investir."</em></div>
<div class="line"><em>"Emacs, j'y comprends rien."</em></div>
<div class="line"><em>"Pycharm, ce n'est qu'à moitié gratuit."</em></div>
<div class="line"><em>"Atom, y paraît que c'est buggé et lent."</em></div>
<div class="line"><br /></div>
</div>
<p>Bon, je vois que tu es un difficile concernant le choix de ton éditeur de code,
et tu as bien raison ! Je t'arrêtes tout de suite en ce qui concerne
<a class="reference external" href="https://atom.io/">Atom</a>. Pour les autres, je ne vais pas rentrer dans le
troll!</p>
<p><em>"C'était"</em> buggé et lent, mais ça ne l'est plus depuis la sortie de la version
stable 1.0 cet été. La version actuelle (1.0.19) marche plutôt bien et
est enfin devenue une alternative viable à
<a class="reference external" href="http://www.sublimetext.com/">Sublime Text</a>.</p>
<p>Comme ST, tu vas pouvoir bénificier:</p>
<ul class="simple">
<li>des curseurs multiples.</li>
<li>de la palette de commandes via <em>ctrl+shift+p</em>.</li>
<li>de la recherche de fichiers via <em>ctrl+p</em>.</li>
<li>d'une intégration Git très poussée.</li>
<li>d'une vue permettant de parcourir tes fichiers.</li>
<li>d'onglets, de panels.</li>
<li>de la coloration syntaxique, de la complétion.</li>
<li>d'une tonnes de plugins.</li>
</ul>
<p>Il n'y a évidemment pas encore autant de plugins que pour ST, mais ça
vient comme tu peux le voir <a class="reference external" href="https://atom.io/packages">ici</a>.</p>
<p>Convaincu ? Prêt pour l'installation ? C'est parti.</p>
<p>Installe-le tout d'abord via les paquets proposés sur le
<a class="reference external" href="https://atom.io/">site officiel</a>.</p>
<p>Avant de l'exécuter, on va installer une série de plugins qui va te permettre
de développer dans de bonnes conditions. Ces plugins vont être:</p>
<ul class="simple">
<li><a class="reference external" href="https://github.com/atom-minimap/minimap">minimap</a>, qui est un
aperçu du code sous forme de minimap à la manière de ST.</li>
<li><a class="reference external" href="https://github.com/atom-community/autocomplete-paths">autocomplete-paths</a>,
qui permet d'activer la complétion des chemins des dossiers et fichiers de ton
système.</li>
<li><a class="reference external" href="https://github.com/sadovnychyi/autocomplete-python">autocomplete-python</a>,
qui est le plugin le plus fiable pour la complétion Python, basé sur
<a class="reference external" href="http://jedi.jedidjah.ch/en/latest/">jedi</a>. Pour activer la complétion des
librairies présentes dans ton virtualenv, il suffit de lancer atom depuis ton
virtualenv.</li>
<li><a class="reference external" href="https://github.com/atom-community/linter">linter</a>, qui permet de visualiser
les erreurs de syntaxe <a class="reference external" href="http://atomlinter.github.io/">de nombreux langages</a>.</li>
<li><a class="reference external" href="https://github.com/AtomLinter/linter-flake8">linter-flake8</a> comme linter python.
On aurait pu en utiliser un autre, comme
<a class="reference external" href="https://github.com/AtomLinter/linter-pylint">linter-pylint</a>, mais je
le trouve trop verbeux.</li>
<li><a class="reference external" href="https://github.com/emmetio/emmet-atom">emmet</a> pour le développement HTML et
CSS.</li>
<li><a class="reference external" href="https://github.com/akonwi/git-plus">git-plus</a>, qui permet d'utiliser toutes
les commandes git depuis Atom.</li>
<li><a class="reference external" href="https://github.com/smashwilson/merge-conflicts">merge-conflicts</a>, qui est un
bon helper lors des <em>git merge</em>.</li>
<li><a class="reference external" href="https://github.com/tombell/travis-ci-status">travis-ci-status</a>, pour
afficher le status de <a class="reference external" href="https://travis-ci.org/">travis</a> dans la barre de
status d'Atom.</li>
<li><a class="reference external" href="https://github.com/kevinsawicki/monokai">monokai</a>, pour obtenir un thème
proche de ST.</li>
<li><a class="reference external" href="https://github.com/abe33/atom-pigments">pigments</a>, pour afficher les
couleurs dans les fichiers CSS, LESS et autres.</li>
<li><a class="reference external" href="https://github.com/thomaslindstrom/color-picker">color-picker</a>, pour choisir
une couleur HTML via <em>ctrl+alt+c</em>.</li>
<li><a class="reference external" href="https://github.com/richrace/highlight-selected">highlight-selected</a>, qui,
lors du double-clique sur un mot, met en surbrillance tous les mots
correspondants, comme ST.</li>
<li><a class="reference external" href="https://github.com/Glavin001/atom-beautify">atom-beautify</a>, qui permet
d'indenter automatiquement le code de nombreux langages pour améliorer la
lisibilité.</li>
<li><a class="reference external" href="https://github.com/Lukasa/language-restructuredtext">language-restructuredtext</a>,
qui active la coloration syntaxique des fichiers <em>.rst</em>.</li>
</ul>
<p>Il en existe d'autres qui pourrait t'intéresser, comme
<a class="reference external" href="https://github.com/atom/vim-mode">vim-mode</a>,
<a class="reference external" href="https://github.com/jeremyramin/terminal-plus">terminal-plus</a>, ou encore
<a class="reference external" href="https://github.com/danielbrodin/atom-project-manager">project-manager</a>, à toi
de faire ton marché!</p>
<p>Atom propose un outil en ligne de
commande, <strong>apm</strong>, qui permet d'installer des plugins sans passer par le menu
<em>Settings</em> d'Atom. On installe donc les plugins:</p>
<div class="highlight"><pre><span></span>apm install minimap
apm install autocomplete-paths
pip install jedi
apm install autocomplete-python
apm install linter
pip install flake8
apm install linter-flake8
apm install emmet
apm install git-plus
apm install merge-conflicts
apm install travis-ci-status
apm install monokai
apm install pigments
apm install color-picker
apm install highlight-selected
apm install atom-beautify
apm install language-restructuredtext
</pre></div>
<p>Tu peux maintenant lancer Atom via la commande:</p>
<div class="highlight"><pre><span></span>atom
</pre></div>
<p>On commence par définir la tabulation comme étant 4 espaces pour être compatible
<a class="reference external" href="https://www.python.org/dev/peps/pep-0008/">pep8</a> dans
<em>Edit->Preferences->Settings</em>:</p>
<pre class="literal-block">
Tab Length
4
</pre>
<p>Puis on active le thème Monokai installé précédemment via
<em>Edit->Preferences->Theme</em>:</p>
<pre class="literal-block">
Syntax Theme
Monokai
</pre>
<p>Et c'est fini! Tout est prêt pour commencer le développement de tes applications
Python et Django.</p>
<p>Tu peux alors créer tes propres
<a class="reference external" href="https://atom.io/docs/latest/using-atom-snippets">snippets</a> en éditant le
fichier <em>snippets.cson</em> du répertoire <em>~/.atom</em> de cette manière par exemple:</p>
<div class="highlight"><pre><span></span><span class="s1">'.source.js'</span><span class="o">:</span>
<span class="s1">'console.log'</span><span class="o">:</span>
<span class="s1">'prefix'</span><span class="o">:</span> <span class="s1">'log'</span>
<span class="s1">'body'</span><span class="o">:</span> <span class="s1">'console.log(${1:"crash"});$2'</span>
</pre></div>
<p>Utilise <em>alt+shift+s</em> pour rechercher tes snippets.</p>
<p>Enfin tu vas également pouvoir sauvegarder ta configuration sur Github de <a class="reference external" href="https://github.com/dotmobo/dotatom">cette
manière</a> par exemple.</p>
Générer son site statique grâce à Pelican2015-10-11T00:00:00+02:002015-10-11T00:00:00+02:00Morgantag:dotmobo.github.io,2015-10-11:/pelican.html<p class="first last">Générer son site statique grâce à Pelican</p>
<img alt="Pelican" class="align-right" src="./images/pelican.png" />
<p><a class="reference external" href="http://blog.getpelican.com/">Pelican</a> est un outil vraiment chouette pour
générer rapidement un site statique comme un blog, afin de le publier via
<a class="reference external" href="https://pages.github.com">Github Pages</a> par exemple.</p>
<p>C'est une alternative Python à <a class="reference external" href="https://jekyllrb.com/">Jekyll</a> (qui lui est
écrit en Ruby), permettant de rédiger ses articles en Markdown. La
principale force de Pelican, c'est de proposer également le <a class="reference external" href="http://sphinx-doc.org/rest.html">reStructuredText</a> comme format d'écriture, ce qui rendra
heureux tous les amoureux de Sphinx.</p>
<p>Pour l'installer, rien de plus simple, l'outil est dispo sur <a class="reference external" href="https://pypi.python.org/pypi">Pypi</a>:</p>
<div class="highlight"><pre><span></span>pip install pelican
</pre></div>
<p>Il propose une commande de génération automatique de projet :</p>
<div class="highlight"><pre><span></span>mkdir ~/dev/myblog <span class="o">&&</span> <span class="nb">cd</span> ~/dev/myblog <span class="o">&&</span> pelican-quickstart
</pre></div>
<p>N'oublie pas de dire oui aux deux questions concernant Github Pages,
et ... c'est tout! Pour proposer des articles, il te suffit de les créer en .rst
dans le dossier <em>content</em>:</p>
<div class="highlight"><pre><span></span>vi ~/dev/myblog/content/mon-article.rst
</pre></div>
<p>Ils devront ressembler à ça:</p>
<div class="highlight"><pre><span></span><span class="gh">Mon article</span>
<span class="gh">###########</span>
<span class="nc">:date:</span> 2015-10-11 10:20
<span class="nc">:modified:</span> 2015-11-04 18:40
<span class="nc">:tags:</span> python,django
<span class="nc">:category:</span> Développement
<span class="nc">:slug:</span> mon-article
<span class="nc">:authors:</span> Toi
<span class="nc">:summary:</span> Mon article concerne blablabla
blablabla ...
</pre></div>
<p>Jetons maintenant un coup d'oeil aux deux fichiers de configuration.</p>
<p>Le premier, <em>pelicanconf.py</em> , est utilisé pour la configuration générale du site:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="c1"># -*- coding: utf-8 -*- #</span>
<span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">unicode_literals</span>
<span class="n">AUTHOR</span> <span class="o">=</span> <span class="s1">'myusername'</span>
<span class="n">SITENAME</span> <span class="o">=</span> <span class="s2">"myblog"</span>
<span class="n">SITEURL</span> <span class="o">=</span> <span class="s1">''</span>
<span class="n">PATH</span> <span class="o">=</span> <span class="s1">'content'</span>
<span class="n">TIMEZONE</span> <span class="o">=</span> <span class="s1">'Europe/Paris'</span>
<span class="n">DEFAULT_LANG</span> <span class="o">=</span> <span class="s1">'fr'</span>
<span class="c1"># Feed generation is usually not desired when developing</span>
<span class="n">FEED_ALL_ATOM</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">CATEGORY_FEED_ATOM</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">TRANSLATION_FEED_ATOM</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">AUTHOR_FEED_ATOM</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">AUTHOR_FEED_RSS</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># Blogroll</span>
<span class="n">LINKS</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># Social widget</span>
<span class="n">SOCIAL</span> <span class="o">=</span> <span class="kc">None</span>
<span class="n">DEFAULT_PAGINATION</span> <span class="o">=</span> <span class="mi">10</span>
<span class="c1"># Uncomment following line if you want document-relative URLs when developing</span>
<span class="n">RELATIVE_URLS</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Theme</span>
<span class="c1"># THEME = "simple"</span>
</pre></div>
<p>Tu remarqueras une partie <em>Theme</em>, on y reviendra plus tard.
Le reste est relativement compréhensible pour se passer d'explication.</p>
<p>Le second, <em>publishconf.py</em> , rajoute les spécificités pour la production:</p>
<div class="highlight"><pre><span></span><span class="ch">#!/usr/bin/env python</span>
<span class="c1"># -*- coding: utf-8 -*- #</span>
<span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">unicode_literals</span>
<span class="c1"># This file is only used if you use `make publish` or</span>
<span class="c1"># explicitly specify it as your config file.</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">curdir</span><span class="p">)</span>
<span class="kn">from</span> <span class="nn">pelicanconf</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">SITEURL</span> <span class="o">=</span> <span class="s1">'http://myusername.github.io'</span>
<span class="n">RELATIVE_URLS</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">FEED_ATOM</span> <span class="o">=</span> <span class="s1">'feeds/all.atom.xml'</span>
<span class="n">DELETE_OUTPUT_DIRECTORY</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Following items are often useful when publishing</span>
<span class="c1">#DISQUS_SITENAME = ""</span>
<span class="c1">#GOOGLE_ANALYTICS = ""</span>
</pre></div>
<p>Il est important ici de définir <strong>SITEURL</strong> et <strong>FEED_ATOM</strong>.
En effet, en mode dev, on n'a pas besoin de générer les flux atom et on utilise
des urls relatives.</p>
<p>Si besoin, tu noteras une compatibilité avec Google Analytics.
Et concernant Disqus, on y reviendra également plus tard.</p>
<p>Pelican propose alors tout un tas de commandes pour générer et déployer ton site.
Au choix, on va pouvoir utiliser <a class="reference external" href="http://www.fabfile.org">Fabric</a>
(malheureusement non disponible en Python 3) ou <strong>make</strong>.</p>
<p>Pour générer ton site en mode dev (utilise <em>pelicanconf.py</em>):</p>
<div class="highlight"><pre><span></span>make html
</pre></div>
<p>Tu peux aussi utiliser un watcher pour automatiser la génération de ton site à chaque modification:</p>
<div class="highlight"><pre><span></span>make regenerate
</pre></div>
<p>Pour servir ton site en mode dev sur <a class="reference external" href="http://localhost:8000/">http://localhost:8000/</a>:</p>
<div class="highlight"><pre><span></span>make serve
</pre></div>
<p>Pour générer ton site pour la production (utilise <em>publishconf.py</em>):</p>
<div class="highlight"><pre><span></span>make publish
</pre></div>
<p>Enfin, pour le diffuser sur le master de ton dépôt Github Pages:</p>
<div class="highlight"><pre><span></span>make github
</pre></div>
<p>Concernant le dépôt Github, je te recommande de commiter le code Pelican sur une
branche à part, et de garder la branche <em>master</em> pour la diffusion du site.
Ton dépôt devra s'appeler <em><username>.github.io</em> pour être compatbile avec
Github Pages.</p>
<p>Par défaut, le design peut sembler austère.</p>
<p>C'est pourquoi on va installer un <a class="reference external" href="http://pelicanthemes.com/">thème</a> un peu
plus moderne utilisant <a class="reference external" href="http://getbootstrap.com/">Bootstrap</a>, comme
<a class="reference external" href="https://github.com/nairobilug/pelican-alchemy">alchemy</a> par exemple.</p>
<p>La plupart des thèmes disponibles sont sous licence MIT, donc n'hésite pas à les
forker pour y apporter tes customisations.</p>
<p>Pour les habitués de <a class="reference external" href="https://www.djangoproject.com/">django</a> et
<a class="reference external" href="http://flask.pocoo.org/">flask</a>, Pelican utilise <a class="reference external" href="http://jinja.pocoo.org/">jinja2</a>
comme moteur de template. Et ça c'est carrément cool.</p>
<p>On installe donc alchemy:</p>
<div class="highlight"><pre><span></span>git clone git@github.com:nairobilug/pelican-alchemy.git ~/dev/pelican-alchemy
pelican-themes -i ~/dev/pelican-alchemy/alchemy
</pre></div>
<p>Et on définit la variable <strong>THEME</strong> de notre fichier de conf <em>pelicanconf.py</em>:</p>
<div class="highlight"><pre><span></span><span class="n">THEME</span> <span class="o">=</span> <span class="s2">"alchemy"</span>
<span class="n">SITE_SUBTEXT</span> <span class="o">=</span> <span class="s2">"Blabla"</span>
<span class="n">PROFILE_IMAGE</span> <span class="o">=</span> <span class="s2">"profil.jpeg"</span>
<span class="n">GITHUB_ADDRESS</span> <span class="o">=</span> <span class="s2">"https://github.com/myusername"</span>
<span class="n">TWITTER_ADDRESS</span> <span class="o">=</span> <span class="s2">"https://twitter.com/myusername"</span>
<span class="n">EXTRA_FAVICON</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">LICENSE_NAME</span> <span class="o">=</span> <span class="s2">"MIT"</span>
<span class="n">LICENSE_URL</span> <span class="o">=</span> <span class="s2">"https://opensource.org/licenses/MIT"</span>
<span class="n">MENU_ITEMS</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">META_DESCRIPTION</span> <span class="o">=</span> <span class="s2">"Blabla</span>
<span class="n">PAGES_ON_MENU</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">CATEGORIES_ON_MENU</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">TAGS_ON_MENU</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">ARCHIVES_ON_MENU</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">SHOW_ARTICLE_AUTHOR</span> <span class="o">=</span> <span class="kc">False</span>
</pre></div>
<p>Concernant les paramètres optionnels, je t'invite à regarder la doc d'alchemy.</p>
<p>"Ok, avoir un blog statique, c'est bien sympa, mais comment je fais pour avoir
des commentaires? Il me faut bien une partie dynamique non ?". Pas de panique,
<a class="reference external" href="https://disqus.com">Disqus</a> est là pour ça.</p>
<p>Il te suffit de te créer un compte sur Disqus et d'y enregistrer ton site en
utilisant le paramétrage <strong>universal-embed-code</strong>. À partir de là, tu vas pouvoir
le configurer via <em>http://<username>.disqus.com/admin/settings</em>.</p>
<p>Enfin, tu l'actives via ton fichier de conf de prod <em>publishconf.py</em>:</p>
<div class="highlight"><pre><span></span><span class="n">DISQUS_SITENAME</span> <span class="o">=</span> <span class="s2">"myusername"</span>
</pre></div>
<p>Tu commites tout ça, tu publies et hop! Ton blog est complètement prêt!</p>
<p>Et si tu veux pousser un peu plus loin l'outil, il y a énormément de
<a class="reference external" href="https://github.com/getpelican/pelican-plugins">plugins</a> disponibles.</p>
Bienvenue, ami(e) développeur(euse)!2015-10-10T00:00:00+02:002015-10-10T00:00:00+02:00Morgantag:dotmobo.github.io,2015-10-10:/bienvenue.html<p class="first last">Bienvenue, ami(e) développeur(euse)!</p>
<p>Tu trouveras sur ce blog tout un tas de trucs concernant le développement en général.</p>
<p>J'y aborderai principalement tout ce qui touche de près ou de loin à l'univers de <a class="reference external" href="https://www.python.org/">python</a> et <a class="reference external" href="https://www.djangoproject.com/">django</a>, en présentant des outils et librairies sympas et des trucs et astuces.</p>
<p>Mais ça ne m’empêchera pas, de temps à autres, de lorgner du côté d'autres langages si je tombe sur d'intéressantes découvertes.</p>
<p>Bonne lecture à tous.</p>