<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrew Healey</title>
    <description>The latest articles on DEV Community by Andrew Healey (@healeycodes).</description>
    <link>https://dev.to/healeycodes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F132886%2F3facc26e-0d97-4a5a-9d7a-2df1d3907ec0.png</url>
      <title>DEV Community: Andrew Healey</title>
      <link>https://dev.to/healeycodes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/healeycodes"/>
    <language>en</language>
    <item>
      <title>Creating Randomness Without Math.random</title>
      <dc:creator>Andrew Healey</dc:creator>
      <pubDate>Wed, 29 Jul 2020 19:53:17 +0000</pubDate>
      <link>https://dev.to/healeycodes/creating-randomness-without-math-random-knj</link>
      <guid>https://dev.to/healeycodes/creating-randomness-without-math-random-knj</guid>
      <description>&lt;p&gt;In JavaScript, you can create random numbers using &lt;code&gt;Math.random()&lt;/code&gt;. But what if we wanted to create our own random values in the browser without this function?&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://tc39.es/ecma262/#sec-math.random" rel="noopener noreferrer"&gt;ECMAScript Language Specification&lt;/a&gt; defines the requirements of &lt;code&gt;Math.random()&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Returns a Number value with positive sign, greater than or equal to 0 but less than 1, chosen randomly or pseudo randomly with approximately uniform distribution over that range, using an implementation-dependent algorithm or strategy. This function takes no arguments.&lt;/p&gt;

&lt;p&gt;Each Math.random function created for distinct realms must produce a distinct sequence of values from successive calls.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Number Generation
&lt;/h2&gt;

&lt;p&gt;Here's an example of a number generator. It uses a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures" rel="noopener noreferrer"&gt;closure&lt;/a&gt; to maintain internal state and creates a sequence of numbers based off an initial seed value. Here the seed is fixed and is always initialized to &lt;code&gt;0&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;

&lt;span class="c1"&gt;// We can iterate through the sequence&lt;/span&gt;
&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;
&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;strong&gt;pseudorandom number generator&lt;/strong&gt; (PRNG) works in a similar manner. A PRNG maintains an internal state and applies math to that state every time a new random number is requested. The seed can be manual or automatic. In the &lt;a href="https://golang.org/pkg/math/rand/#New" rel="noopener noreferrer"&gt;Go programming language&lt;/a&gt;, you must seed &lt;code&gt;math/rand&lt;/code&gt; yourself. In the browser, &lt;code&gt;Math.random&lt;/code&gt; requests random data under the hood from the operating system (OS) to use as a seed.&lt;/p&gt;

&lt;p&gt;PRNGs are deterministic. The same seed will always produce the same sequence of numbers. Often, a deterministic outcome is preferred. For example, to generate the same random events on all clients without them having to talk over a network. Or for reproducible performance benchmarks.&lt;/p&gt;

&lt;p&gt;A hash function can be used to create a PRNG. In &lt;a href="https://github.com/v8/v8/blob/4b9b23521e6fd42373ebbcb20ebe03bf445494f9/benchmarks/spinning-balls/v.js" rel="noopener noreferrer"&gt;spinning-balls&lt;/a&gt;, one of Chrome's benchmarks, we can see an example of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// v8/benchmarks/spinning-balls/v.js&lt;/span&gt;

&lt;span class="c1"&gt;// To make the benchmark results predictable, we replace Math.random&lt;/span&gt;
&lt;span class="c1"&gt;// with a 100% deterministic alternative.&lt;/span&gt;
&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;49734321&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Robert Jenkins' 32 bit integer hash function.&lt;/span&gt;
    &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xffffffff&lt;/span&gt;
    &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mh"&gt;0x7ed55d16&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xffffffff&lt;/span&gt;
    &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="mh"&gt;0xc761c23c&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xffffffff&lt;/span&gt;
    &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mh"&gt;0x165667b1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xffffffff&lt;/span&gt;
    &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mh"&gt;0xd3a2646c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xffffffff&lt;/span&gt;
    &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mh"&gt;0xfd7046c5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xffffffff&lt;/span&gt;
    &lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="mh"&gt;0xb55a4f09&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xffffffff&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xfffffff&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mh"&gt;0x10000000&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like our number generator, it alters its internal state while calculating the next random number. This state-change allows the next call to produce a different number.&lt;/p&gt;

&lt;h2&gt;
  
  
  More on Pseudorandom Number Generators
&lt;/h2&gt;

&lt;p&gt;One of the oldest and most well known types of PRNG is the &lt;a href="https://en.wikipedia.org/wiki/Linear_congruential_generator" rel="noopener noreferrer"&gt;linear congruential generator&lt;/a&gt; (LCG). Which, despite its somewhat scary name, does not require many lines of code.&lt;/p&gt;

&lt;p&gt;@bryc provides an example and &lt;a href="https://github.com/bryc/code/blob/master/jshash/PRNGs.md#lcg-lehmer-rng" rel="noopener noreferrer"&gt;a warning&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Commonly called a Linear congruential generator (LCG), but in this case, more correctly called a Multiplicative congruential generator (MCG) or Lehmer RNG. It has a state and period of 2^31-1. It's blazingly fast in JavaScript (likely the fastest), but its quality is quite poor.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;LCG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;48271&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2147483647&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;2147483647&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2147483648&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(This is the first time I've come across &lt;code&gt;Math.imul()&lt;/code&gt; — which provides &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul#Description" rel="noopener noreferrer"&gt;C-like 32-bit multiplication&lt;/a&gt; of the two parameters.)&lt;/p&gt;

&lt;p&gt;What does @bryc's comment, "its quality is quite poor" mean in this context? Well, given certain even seeds, this algorithm has a pattern when the final step (the division) is removed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// https://gist.github.com/blixt/f17b47c62508be59987b#gistcomment-2792771&lt;/span&gt;

&lt;span class="c1"&gt;// @bryc:&lt;/span&gt;
&lt;span class="c1"&gt;// "Looking at the output without the division, and in hexadecimal, the&lt;/span&gt;
&lt;span class="c1"&gt;// first bits are always the same. This shows a clear pattern in the&lt;/span&gt;
&lt;span class="c1"&gt;// first 8 bits of the output: 1000 000, and it happens each time,&lt;/span&gt;
&lt;span class="c1"&gt;// infinitely. This is mostly caused by using an even seed."&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LCG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;imul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;48271&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nxt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LCG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3816034944&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nxt&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/* Outputs:
4b6c5580 &amp;lt;-- notice the last two digits
b04dc280 &amp;lt;--
9645a580
16717280
d974f580
5c9f2280
9a3a4580
f196d280
b5d59580 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are &lt;a href="https://en.wikipedia.org/wiki/Diehard_tests" rel="noopener noreferrer"&gt;many&lt;/a&gt; ways to test the quality of randomness. Some of the methodology and results of these tests can be understood by a layperson. One of the &lt;a href="https://en.wikipedia.org/wiki/Diehard_tests" rel="noopener noreferrer"&gt;Diehard battery of tests&lt;/a&gt; plays 200000 games of craps and looks at the distribution of wins and the number of throws each game.&lt;/p&gt;

&lt;p&gt;There's also a test for LCGs called the &lt;a href="https://en.wikipedia.org/wiki/Spectral_test" rel="noopener noreferrer"&gt;spectral test&lt;/a&gt; which plots the sequence in two or more dimensions. In the example below, we can see the &lt;a href="https://en.wikipedia.org/wiki/Hyperplane" rel="noopener noreferrer"&gt;hyperplanes&lt;/a&gt; that the spectral test measures for.&lt;/p&gt;

&lt;center&gt;

![Hyperplanes of an LCG in three dimensions](https://dev-to-uploads.s3.amazonaws.com/i/rq9ftbb8fm6l7e3oy142.gif)

&lt;/center&gt;

&lt;p&gt;A PRNG eventually repeats its sequence. In this context, the &lt;em&gt;period&lt;/em&gt; is the length of steps until the cycle repeats. Simpler PRNGs such as &lt;a href="https://github.com/bryc/code/blob/master/jshash/PRNGs.md#mulberry32" rel="noopener noreferrer"&gt;Mulberry32&lt;/a&gt; have a period as low as ~4 billion whereas the &lt;a href="https://stackoverflow.com/a/38838863" rel="noopener noreferrer"&gt;Mersenne Twister&lt;/a&gt; has a period of &lt;code&gt;2^19,937 - 1&lt;/code&gt;. In 2015, the V8 team &lt;a href="https://v8.dev/blog/math-random" rel="noopener noreferrer"&gt;said&lt;/a&gt; that their implementation of &lt;code&gt;Math.random()&lt;/code&gt; uses an algorithm called &lt;a href="http://vigna.di.unimi.it/ftp/papers/xorshiftplus.pdf" rel="noopener noreferrer"&gt;xorshift128+&lt;/a&gt; which has a period of &lt;code&gt;2^128 - 1&lt;/code&gt;. Its introduction can been seen in &lt;a href="https://github.com/v8/v8/blob/085fed0fb5c3b0136827b5d7c190b4bd1c23a23e/src/base/utils/random-number-generator.h#L102" rel="noopener noreferrer"&gt;this diff&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If a PRNG eventually repeats itself, you might wonder why we call it repeatedly. Why not use the first number and then reset the internal state with a new seed? The problem with this is that the seed needs to originate from somewhere. If we continue to ask the OS for more random data there is a chance that the call may block (as the OS waits for more randomness to be generated) and our program will stall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Entropy Required
&lt;/h2&gt;

&lt;p&gt;So you've settled on a PRNG and replaced &lt;code&gt;window.Math.random&lt;/code&gt;. You've shipped it to your users and, at first, everyone seems to be happy.&lt;/p&gt;

&lt;p&gt;But wait! You forgot about the seed. And now your users are complaining about the sequence of random numbers they get. It's the same every time their customers' page loads. All of their software is predictable. As a result, the web games they built are easy to beat.&lt;/p&gt;

&lt;p&gt;Huzaifa Sidhpurwala &lt;a href="https://www.redhat.com/en/blog/understanding-random-number-generators-and-their-limitations-linux" rel="noopener noreferrer"&gt;reminds us&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Entropy is the measurement of uncertainty or disorder in a system. Good entropy comes from the surrounding environment which is unpredictable and chaotic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When required, the generation of securely random numbers in the browser is performed by &lt;code&gt;Crypto.getRandomValues()&lt;/code&gt; from the &lt;a href="https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues" rel="noopener noreferrer"&gt;Web Cryptography API&lt;/a&gt;. Which is seeded by "a platform-specific random number function, the Unix &lt;code&gt;/dev/urandom&lt;/code&gt; device, or other source of random or pseudorandom data."&lt;/p&gt;

&lt;p&gt;The Linux &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/char/random.c" rel="noopener noreferrer"&gt;source&lt;/a&gt; suggests where this pseudorandom data can come from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sources of randomness from the environment include inter-keyboard timings, inter-interrupt timings from some interrupts, and other events which are both (a) non-deterministic and (b) hard for an outside observer to measure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are also hardware devices that use &lt;a href="https://en.wikipedia.org/wiki/Hardware_random_number_generator#Quantum_random_properties" rel="noopener noreferrer"&gt;quantum mechanical physical randomness&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can find many &lt;a href="https://en.wikipedia.org/wiki/Random_number_generator_attack#Prominent_examples" rel="noopener noreferrer"&gt;prominent examples&lt;/a&gt; of random number generator attacks which occurred because the wrong type (or not enough) entropy was used. Cloudflare &lt;a href="https://www.cloudflare.com/learning/ssl/lava-lamp-encryption/" rel="noopener noreferrer"&gt;famously&lt;/a&gt; uses lava lamps as an entropy source. Since we are not attempting to create a secure algorithm, predictable sources of entropy like time are fine.&lt;/p&gt;

&lt;p&gt;We can use &lt;code&gt;Date.now()&lt;/code&gt; our seed state. This means that we will get a different random sequence for every millisecond. We could also use &lt;code&gt;performance.now()&lt;/code&gt; which returns the length of time since the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#The_time_origin" rel="noopener noreferrer"&gt;time origin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Other possible ways of getting entropy in the browser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;crypto.getRandomValues&lt;/code&gt;, &lt;code&gt;crypto&lt;/code&gt; key generation, or similar (feels like cheating)&lt;/li&gt;
&lt;li&gt;Mouse/touch events, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Ambient_Light_Events" rel="noopener noreferrer"&gt;ambient light events&lt;/a&gt;, mic/webcam noise (hard to use on page load)&lt;/li&gt;
&lt;li&gt;Geolocation API, Bluetooth API, or similar (need permission, doesn't work on page load)&lt;/li&gt;
&lt;li&gt;WebGL/video performance shenanigans&lt;/li&gt;
&lt;li&gt;Most APIs &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API" rel="noopener noreferrer"&gt;listed here&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's our slower (because it's not native code) and unstable (because I haven't tested it) replacement for &lt;code&gt;Math.random()&lt;/code&gt;. Also note that PRNGs have requirements for the seed state (e.g. prime numbers, 128-bit). Our algorithm doesn't comply with the &lt;a href="http://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf" rel="noopener noreferrer"&gt;seed recommendations&lt;/a&gt; for the Xoshiro family.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// https://github.com/bryc/code/blob/master/jshash/PRNGs.md&lt;/span&gt;
&lt;span class="c1"&gt;// xoshiro128+ (128-bit state generator in 32-bit)&lt;/span&gt;
&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;xoshiro128p&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Using the same value for each seed is _screamingly_ wrong&lt;/span&gt;
  &lt;span class="c1"&gt;// but this is 'good enough' for a toy function.&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;
    &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;
    &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;
    &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;
    &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4294967296&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;

&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 0.5351827056147158&lt;/span&gt;
&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 0.2675913528073579&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  So, Mission Accomplished?
&lt;/h2&gt;

&lt;p&gt;Sadly it's impossible to create a fully ECMAScript compliant replacement for &lt;code&gt;Math.random()&lt;/code&gt; since the specification requires "distinct realms [to] produce a distinct sequence of values from successive calls." A &lt;em&gt;realm&lt;/em&gt; roughly means a different global environment (e.g. a different window, or a different &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API" rel="noopener noreferrer"&gt;WebWorker&lt;/a&gt;). Our version cannot reach outside its realm thus cannot make this guarantee.&lt;/p&gt;

&lt;p&gt;However, there have been proposals for a &lt;a href="https://github.com/tc39/proposal-realms" rel="noopener noreferrer"&gt;Realms API&lt;/a&gt;. It's not inconceivable that such an API would provide access to something like an incrementing realm id. This would give our algorithm the loophole it needs — access to Realm-unique entropy!&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Thanks to &lt;a href="https://commons.wikimedia.org/wiki/File:Lcg_3d.gif" rel="noopener noreferrer"&gt;JN~commonswiki&lt;/a&gt; for the 3D GIF of the spectral test.&lt;/small&gt;&lt;/p&gt;




&lt;p&gt;Join 300+ people signed up to my &lt;a href="https://buttondown.email/healeycodes" rel="noopener noreferrer"&gt;newsletter&lt;/a&gt; about code and how I write it!&lt;/p&gt;

&lt;p&gt;I tweet about tech &lt;a href="https://twitter.com/healeycodes" rel="noopener noreferrer"&gt;@healeycodes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>computerscience</category>
      <category>programming</category>
    </item>
    <item>
      <title>A Practical Introduction to Graphs (Network Diagrams)</title>
      <dc:creator>Andrew Healey</dc:creator>
      <pubDate>Wed, 08 Jul 2020 06:42:28 +0000</pubDate>
      <link>https://dev.to/healeycodes/a-practical-introduction-to-graphs-network-diagrams-b2b</link>
      <guid>https://dev.to/healeycodes/a-practical-introduction-to-graphs-network-diagrams-b2b</guid>
      <description>&lt;p&gt;A graph is a structure that represents the connections between objects. They can be used for social network analysis, working out someone's &lt;a href="https://en.wikipedia.org/wiki/Erd%C5%91s_number"&gt;Erdős number&lt;/a&gt;, finding the shortest path through a maze, and many other &lt;a href="https://en.wikipedia.org/wiki/Graph_theory#Applications"&gt;applications&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We'll be calculating the path between two countries with the least border crossings. The method we'll use is &lt;a href="https://en.wikipedia.org/wiki/Breadth-first_search"&gt;Breadth-First Search&lt;/a&gt; (BFS).&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in a Graph?
&lt;/h2&gt;

&lt;p&gt;Graphs are made up of vertices and edges. Below is the representation of France and its land border crossings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hshw29hu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/204moqnobc44hphvi1n2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hshw29hu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/204moqnobc44hphvi1n2.png" alt="A graph of France and its land borders. Country codes are uses for the vertices."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An example of a vertex is &lt;code&gt;FR&lt;/code&gt; or &lt;code&gt;DE&lt;/code&gt; — they are also known as points or nodes. Here they represent countries.&lt;/p&gt;

&lt;p&gt;An example of an edge is &lt;code&gt;{FR, IT}&lt;/code&gt; or &lt;code&gt;{FR, BE}&lt;/code&gt; — they are also known as lines, links, or connections. Here they represent the land border crossing between two countries.&lt;/p&gt;

&lt;p&gt;Let's build a slightly more complicated graph. If our goal is to find the shortest path between two countries then we need a structure containing all of the countries of the world. We'll actually need multiple graphs because some sets of countries aren't connected to other sets of countries (and islands are just lone vertices).&lt;/p&gt;

&lt;p&gt;All the code for this post can be found on &lt;a href="https://github.com/healeycodes/country-borders"&gt;healeycodes/country-borders&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We can get all the data we need from &lt;code&gt;borders.csv&lt;/code&gt;. This file is structured as &lt;code&gt;country_code, country_name, country_border_code, country_border_name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As a side-quest, we'll also store a relationship between country codes and their full names to make our output more user-friendly later on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;csv&lt;/span&gt;

&lt;span class="n"&gt;country_border_graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;country_code_lookup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'borders.csv'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;csvfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;csvfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delimiter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# skip the headers
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;country_code_lookup&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;country_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;country_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;country_a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;country_border_graph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;country_border_graph&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;country_a&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;country_border_graph&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;country_a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;country_b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A graph is a mathematical structure and there is no singular way to represent them in code. Here, we have chosen a dictionary that maps a country to a list of its neighbors.&lt;/p&gt;

&lt;p&gt;If we isolate France inside &lt;code&gt;country_border_graph&lt;/code&gt;, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;'FR'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'AD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'BE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'DE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'IT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'LU'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'MC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'ES'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'CH'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Displaying Graphs
&lt;/h2&gt;

&lt;p&gt;So how do we print this to the screen? First we alter the structure of our data then we plug it into some libraries.&lt;/p&gt;

&lt;p&gt;In order to build a &lt;a href="https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html"&gt;DataFrame&lt;/a&gt; for the &lt;code&gt;pandas&lt;/code&gt; library, we need to create two lists that describe all of our edges. We do that by iterating through the structure and adding a vertex to two lists (&lt;code&gt;from_verts&lt;/code&gt; and &lt;code&gt;to_verts&lt;/code&gt;) when we want to describe a connection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;from_verts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;to_verts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;country_border_graph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country_border_graph&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;])):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;country_border_graph&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# skip islands
&lt;/span&gt;            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;from_verts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;to_verts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country_border_graph&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For France, the lists would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# from_verts
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'FR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'FR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'FR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'FR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'FR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'FR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'FR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'FR'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# to_verts
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'AD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'BE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'DE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'IT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'LU'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'MC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'ES'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'CH'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;pandas&lt;/code&gt;, &lt;code&gt;networkx&lt;/code&gt;, and &lt;code&gt;matplotlib&lt;/code&gt; we can display our non-islands in a popout window.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;networkx&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from_verts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_verts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'from'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;from_verts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'to'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;to_verts&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;G&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_pandas_edgelist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'from'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'to'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;G&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;with_labels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;node_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;node_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"skyblue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;node_shape&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"o"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;linewidths&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;font_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;font_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"grey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;font_weight&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bold"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;edge_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"grey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;dpi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The result is below. (I briefly attempted to visually &lt;em&gt;unsquish&lt;/em&gt; the vertices from each other but didn't think it was worth the effort. If you want to display this data more aesthetically, start by checking out this &lt;a href="https://stackoverflow.com/a/54876985"&gt;StackOverflow answer&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8ddtXLB3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jua3hdzlfyba5exdi42k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8ddtXLB3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/jua3hdzlfyba5exdi42k.png" alt="A graph of all the non-islands of the world"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While this image, at first glance, looks similar to a world map the positions of the countries are not tied to their position on Earth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shortest paths
&lt;/h2&gt;

&lt;p&gt;You can calculate the shortest path between some countries by hand. Take the path from Canada to Mexico as an example. At the lowest point of the largest set in the above image, we can see that &lt;code&gt;CA&lt;/code&gt; is connected to &lt;code&gt;US&lt;/code&gt; which is connected to &lt;code&gt;MX&lt;/code&gt;. It's clear there is only one route between Canada and Mexico and therefore we have found the shortest path.&lt;/p&gt;

&lt;p&gt;What if there are multiple paths? Let's take the path from France to China as an example.&lt;/p&gt;

&lt;p&gt;Breadth-First Search (BFS) works by first looking at all of the nearby vertices and then working further and further away from the start point. By using &lt;code&gt;FR&lt;/code&gt; as a starting point, our algorithm will first check &lt;code&gt;['AD', 'BE', 'DE', 'IT', 'LU', 'MC', 'ES', 'CH']&lt;/code&gt;. When it doesn't find China among those, it will then look at the neighbors of &lt;code&gt;AD&lt;/code&gt; and then the neighbors of &lt;code&gt;BE&lt;/code&gt; and so on.&lt;/p&gt;

&lt;p&gt;In the diagram below, we can see the order in which BFS would visit each vertex of a tree.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wkx7VpQx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gv55shbx0a23g3lkud03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wkx7VpQx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gv55shbx0a23g3lkud03.png" alt="Nodes, numbered 1-to-12 in a tree"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The algorithm we use has been adapted from an &lt;a href="https://www.python.org/doc/essays/graphs/"&gt;outdated essay&lt;/a&gt; on Python.org. Eryk Kopczyński corrected the efficiency of the essay's pathing function ten years after its publication date. In linear time, it computes and stores the direct paths from the start point to every other point and then returns the path from the start point to the endpoint.&lt;/p&gt;

&lt;p&gt;His original code returns the path in a strange format. &lt;code&gt;FR&lt;/code&gt; to &lt;code&gt;CN&lt;/code&gt; is &lt;code&gt;[[[[['FR'], 'DE'], 'PL'], 'RU'], 'CN']&lt;/code&gt;. We can unfurl this nested list with &lt;code&gt;flatten&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Iterable&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# flatten a nested list
&lt;/span&gt;    &lt;span class="c1"&gt;# https://stackoverflow.com/a/2158532
&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Iterable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_shortest_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# breadth first search (BFS)
&lt;/span&gt;    &lt;span class="c1"&gt;# adapted from https://www.python.org/doc/essays/graphs/
&lt;/span&gt;
    &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;at&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;raw_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can use this function on the original &lt;code&gt;country_border_graph&lt;/code&gt; dictionary we built. With the &lt;code&gt;country_code_lookup&lt;/code&gt; we made, we can print out user-friendly information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;france&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;country_border_graph&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"FR"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;france_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;country_code_lookup&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;france&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;f'France is connected to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;france_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; countries.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;f'They are &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;france_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_shortest_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country_border_graph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'FR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'CN'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;path_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;country_code_lookup&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;f'The shortest path from France to China is: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s"&gt;" -&amp;gt; "&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This prints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;France is connected to 8 countries.
They are Andorra, Belgium, Germany, Italy, Luxembourg, Monaco, Spain, Switzerland.
The shortest path from France to China is: France -&amp;gt; Germany -&amp;gt; Poland -&amp;gt; Russian Federation -&amp;gt; China.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To display this path, we can use the &lt;code&gt;show&lt;/code&gt; function we defined earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;find_shortest_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;country_border_graph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'FR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'CN'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;from_verts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;to_verts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="c1"&gt;# we're drawing a line
&lt;/span&gt;    &lt;span class="c1"&gt;# so we describe each edge 
&lt;/span&gt;    &lt;span class="n"&gt;from_verts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;to_verts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from_verts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_verts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VNn2ujMx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/16h1f9b7md1uor7upvlt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VNn2ujMx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/16h1f9b7md1uor7upvlt.png" alt="A line of nodes, FR-DE-PL-RU-CN"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can now visually show the shortest path between any two connected countries.&lt;/p&gt;

&lt;p&gt;(If we attempted to use this code to calculate the path between two non-connected countries, e.g. &lt;code&gt;GB&lt;/code&gt; and &lt;code&gt;CN&lt;/code&gt;, the code would throw an error. Let's blame geography for this instead of the code's author.)&lt;/p&gt;

&lt;p&gt;&lt;small&gt;Thanks to &lt;a href="https://commons.wikimedia.org/wiki/File:Breadth-first-tree.svg"&gt;Alexander Drichel&lt;/a&gt; for the Breadth-First Tree diagram.&lt;/small&gt;&lt;/p&gt;




&lt;p&gt;Join 300+ people signed up to my &lt;a href="https://buttondown.email/healeycodes"&gt;newsletter&lt;/a&gt; about code and how I write it!&lt;/p&gt;

&lt;p&gt;I tweet about tech &lt;a href="https://twitter.com/healeycodes"&gt;@healeycodes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>codenewbie</category>
    </item>
    <item>
      <title>Polyfilling the Fetch API for Old Browsers and Node.js</title>
      <dc:creator>Andrew Healey</dc:creator>
      <pubDate>Sun, 28 Jun 2020 09:33:26 +0000</pubDate>
      <link>https://dev.to/healeycodes/polyfilling-the-fetch-api-for-old-browsers-and-node-js-5ap1</link>
      <guid>https://dev.to/healeycodes/polyfilling-the-fetch-api-for-old-browsers-and-node-js-5ap1</guid>
      <description>&lt;p&gt;First some definitions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A &lt;strong&gt;polyfill&lt;/strong&gt; will try to emulate certain APIs, so can use them as if they were already implemented.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;transpiler&lt;/strong&gt; on the other hand will transform your code and replace the respective code section by other code, which can already be executed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(Thanks &lt;a href="https://stackoverflow.com/a/31206361"&gt;Sirko&lt;/a&gt; for those!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples of polyfilling and transpiling
&lt;/h2&gt;

&lt;p&gt;By polyfilling the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API"&gt;Fetch API&lt;/a&gt; we make it usable in browsers where it isn't available by default. Another example would be making it available in its original functionality in Node.js.&lt;/p&gt;

&lt;p&gt;By transpiling &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax"&gt;Spread syntax&lt;/a&gt; (an ES6 feature) into ES5 compatible JavaScript, we end up with source code that is easier for us to write and deployable code that works in older browsers!&lt;/p&gt;

&lt;p&gt;Here's our ES6 code example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Here is that same code transpiled into ES5 compatible code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_console&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_console&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_console&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;(Technically, this is the &lt;a href="https://babeljs.io/docs/en/babel-plugin-transform-spread#loose"&gt;loose&lt;/a&gt; transpilation because otherwise the above snippet would be eleven times longer.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;fetch&lt;/code&gt; in old browsers
&lt;/h2&gt;

&lt;p&gt;We love the Fetch API because it means we can avoid using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest"&gt;XMLHttpRequest&lt;/a&gt;. Instead of providing a callback, we can use lovely &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise"&gt;Promises&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's a &lt;code&gt;fetch&lt;/code&gt; call that prints out the status code of a request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://httpstat.us&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If we tried running that in Internet Explorer 11 (or Edge 13, or Chrome 39, and so on) we would get an error.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;window.fetch&lt;/code&gt; would likely evaluate to undefined. We might get an error that looks like &lt;code&gt;Uncaught TypeError: window.fetch is not a function&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's easy to check one-off functionality for a feature on &lt;em&gt;Can I use&lt;/em&gt; — here's &lt;code&gt;fetch&lt;/code&gt; &lt;a href="https://caniuse.com/#search=fetch"&gt;https://caniuse.com/#search=fetch&lt;/a&gt;. It isn't viable to check every feature that your code might use, so that's why we use things like &lt;a href="https://babeljs.io/docs/en/babel-preset-env"&gt;@babel/preset-env&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;@babel/preset-env&lt;/code&gt; is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s). This both makes your life easier and JavaScript bundles smaller!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An even cooler feature of Babel is that it allows us to specify which platforms we want to support.&lt;/p&gt;

&lt;p&gt;Why not just support &lt;em&gt;all&lt;/em&gt; platforms? Because then the JavaScript bundle we send to our users would get larger and larger every year and website performance would grind to a halt.&lt;/p&gt;

&lt;p&gt;Babel's &lt;a href="https://babeljs.io/docs/en/babel-preset-env#browserslist-integration"&gt;Browserslist Integration&lt;/a&gt; lets us forget about version numbers and instead use handy shortcuts. Let's say we wanted to support 99.75% of browsers and no dead browsers. We can add &lt;code&gt;"browserslist": "&amp;gt; 0.25%, not dead"&lt;/code&gt; to our &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;However, you might be reading this because you found out that &lt;code&gt;@babel/preset-env&lt;/code&gt; doesn't include a &lt;code&gt;fetch&lt;/code&gt; polyfill. This is an open issue on GitHub (&lt;a href="https://github.com/babel/babel/issues/9160"&gt;#9160&lt;/a&gt;). It looks like it's going to stay this way.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Babel only adds polyfill for ECMAScript methods (defined at &lt;a href="https://tc39.github.io/ecma262/"&gt;https://tc39.github.io/ecma262/&lt;/a&gt;). Since fetch is defined by a web specification (&lt;a href="https://fetch.spec.whatwg.org/"&gt;https://fetch.spec.whatwg.org/&lt;/a&gt;), you need to add the polyfill yourself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's okay because we can use &lt;a href="https://github.com/github/fetch#usage"&gt;github/fetch&lt;/a&gt; to polyfill it for us.&lt;/p&gt;

&lt;p&gt;Either by replacing all instances of &lt;code&gt;fetch&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;whatwg-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Or on a case-by-case basis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fetchPolyfill&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;whatwg-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;   &lt;span class="c1"&gt;// use native browser version&lt;/span&gt;
&lt;span class="nx"&gt;fetchPolyfill&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;  &lt;span class="c1"&gt;// use polyfill implementation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Using &lt;code&gt;fetch&lt;/code&gt; in Node.js
&lt;/h2&gt;

&lt;p&gt;The Fetch API is common and people are fluent with it. It's productive if they can use it in all the JavaScript they write. Many people think it's available in Node.js by default. It's not but &lt;em&gt;there's a package for that&lt;/em&gt; (™).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/node-fetch/node-fetch"&gt;node-fetch/node-fetch&lt;/a&gt; let's us use the API we're fluent with to make HTTP calls on the back-end. Underneath, it uses native Node.js functionality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CommonJS&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ES Module&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you're looking for an isomorphic solution (this means using the same code in the browser and in Node.js) then you'll want Jason Miller's &lt;a href="https://www.npmjs.com/package/isomorphic-unfetch"&gt;isomorphic-unfetch&lt;/a&gt; (but not for React Native, see &lt;a href="https://github.com/matthew-andrews/isomorphic-fetch/issues/125"&gt;#125&lt;/a&gt;) or Leonardo Quixada's &lt;a href="https://github.com/lquixada/cross-fetch"&gt;cross-fetch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These solutions will figure out which environment you're in and choose the correct polyfill.&lt;/p&gt;




&lt;p&gt;Join 300+ people signed up to my &lt;a href="https://buttondown.email/healeycodes"&gt;newsletter&lt;/a&gt; about code and how I write it!&lt;/p&gt;

&lt;p&gt;I tweet about tech &lt;a href="https://twitter.com/healeycodes"&gt;@healeycodes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Flow of Knowledge in a Team (or Why I Dig Tickets)</title>
      <dc:creator>Andrew Healey</dc:creator>
      <pubDate>Tue, 02 Jun 2020 07:40:16 +0000</pubDate>
      <link>https://dev.to/healeycodes/the-flow-of-knowledge-in-a-team-or-why-i-dig-tickets-2l5l</link>
      <guid>https://dev.to/healeycodes/the-flow-of-knowledge-in-a-team-or-why-i-dig-tickets-2l5l</guid>
      <description>&lt;p&gt;Engineering tickets often end up meaning a lot. For the product but also emotionally for the individual. It's the engineer's job to be involved in the whole process from before the ticket even exists. Engineers refine the ticket, estimate the ticket, and shuffle it through columns on a sprint board until customers receive a feature.&lt;/p&gt;

&lt;p&gt;The way products are built varies at every company. A team I sit metres away from at work do things so differently that there would be a learning curve if I joined them. But the concepts remain the same.&lt;/p&gt;

&lt;p&gt;The fun part about writing this will be hearing about all the different ways y'all make software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature or fix?
&lt;/h2&gt;

&lt;p&gt;A ticket can be a feature that needs to be completed, a bug that requires solving, or a chore that needs doing (e.g. code clean-up, documentation creation). A &lt;em&gt;spike&lt;/em&gt; is a type of ticket that means the situation is unclear. Perhaps it's because there are unknowns about the technical implementation. Perhaps what's required is checking where a tricky bug is originating from. A spike can sometimes mean: go and figure out what tickets need to be created to solve this problem and then create those tickets (the hydra of tickets).&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://en.wikipedia.org/wiki/Agile_software_development"&gt;Agile&lt;/a&gt; methodology, tickets shouldn't be time boxed but spikes are one of the exceptions. Without a time-limit, when does the engineering stop if unknowns are involved? It's far better to write down your findings and get your team's input. Sometimes spikes are easier than they seem and the development work can be completed within the time-limit and everyone is happy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where do tickets come from?
&lt;/h2&gt;

&lt;p&gt;Here are some ways that tickets can come into existence from nothing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A customer complains about a bug and a support engineer documents it&lt;/li&gt;
&lt;li&gt;A project manager or team lead plans a piece of the project&lt;/li&gt;
&lt;li&gt;Some technical debt is created while working on another ticket&lt;/li&gt;
&lt;li&gt;A developer realizes that their current ticket requires additional parts&lt;/li&gt;
&lt;li&gt;Another department (e.g. data scientists) require some work to be done&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where do tickets live?
&lt;/h2&gt;

&lt;p&gt;Most tickets exist in a sad place called the backlog. It's sad because there are always tickets in there that you want to complete. Things that genuinely improve the product (or your ease of working on the product).&lt;/p&gt;

&lt;p&gt;Backlogs usually increase in size until they become unmanageable. At which point they can be filtered and trimmed by certain criteria. &lt;em&gt;Won't do&lt;/em&gt; is a common reason to close a ticket — as in: we realized that there is an issue here but we don't have any plans to actually fix the problem. I've seen date cut-off limits where after a year of a ticket existing if it hasn't been completed then it's put out of its misery and archived. A well trimmed backlog is the sign of a well managed engineering team.&lt;/p&gt;

&lt;p&gt;Tickets are always archived, never deleted. If there once was a bug that then becomes a bigger bug in the future, you'll wish there was a trail to follow when it falls on your lap.&lt;/p&gt;

&lt;p&gt;Tickets are brought out of the backlog into a refinement section. These tickets are then discussed in a backlog refining meeting. Questions are raised and answered before the ticket can continue on its journey. Sometimes the questions can't be answered and a comment is added. A comment might tag someone with a TODO like &lt;em&gt;&lt;a class="comment-mentioned-user" href="https://dev.to/alice"&gt;@alice&lt;/a&gt;
 to gather more requirements from (other team who requested the feature)&lt;/em&gt;. This ticket will be looked at again in the next meeting.&lt;/p&gt;

&lt;p&gt;In a backlog refinement meeting, you might ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this possible?&lt;/li&gt;
&lt;li&gt;Is this within our area of responsibility (is this another team's job)?&lt;/li&gt;
&lt;li&gt;Roughly, what steps are involved in solving this?&lt;/li&gt;
&lt;li&gt;How long will this take? &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good rule for completing projects is: don't do something if you don't actually need to do it. This sounds silly until you realise how often it is broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estimation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Planning_poker"&gt;Planning poker&lt;/a&gt; is a ritual that helps get the average estimation for a ticket from a group of engineers. It works best when everyone reveals their estimations at the same time as this removes biases. Planning poker cards are often used because everyone can flip them over at the same time.&lt;/p&gt;

&lt;p&gt;The unit of time is 'points' which can seem abstract at first. It's common to hear things like &lt;em&gt;oh that's just a one-pointer&lt;/em&gt; or &lt;em&gt;hm, 13 points is a little high, can we break that up into smaller tickets?&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Planning poker cards have a modified Fibonacci sequence on them. 1, 2, 3, 5, 8, 13, 20, 40, and 100. Why? The International Scrum Institute has a &lt;a href="https://www.scrum-institute.org/Effort_Estimations_Planning_Poker.php"&gt;good summary&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The reason for using the Fibonacci sequence is to reflect the uncertainty in estimating larger items. A high estimate usually means that the story is not well understood in detail or should be broken down into multiple smaller stories. Smaller stories can be estimated in greater detail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When tickets are pointed they can be placed into &lt;em&gt;sprints&lt;/em&gt; in a sprint planning meeting (&lt;a href="https://www.scrum.org/resources/what-is-a-sprint-in-scrum"&gt;What is a Sprint in Scrum?&lt;/a&gt;). A sprint is a time frame where a goal is worked towards by completing tickets. Traditionally, a working product is demoed to stakeholders at the end of a sprint so that it can be worked on iteratively taking feedback into account.&lt;/p&gt;

&lt;p&gt;One sprint follows another. At the end of a sprint there are reviews and retrospectives to ensure that the ways of working are productive and pleasant for everyone involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interacting with tickets
&lt;/h2&gt;

&lt;p&gt;When I pick up a ticket, I move it to a column called &lt;em&gt;In Progress&lt;/em&gt;. This automatically assigns me to the ticket and puts my avatar in the corner of the card. This stops people doing duplicate work and helps product managers and quality assurance engineers see who is working on what.&lt;/p&gt;

&lt;p&gt;Sprint boards usually have a column for tickets that are in review. GitHub notifications can get lost in a sea of other emails so being able to see what work I can review for my teammates really helps me.&lt;/p&gt;

&lt;p&gt;The act of dragging these small cards in the sprint board's UI feels significant. Moving the ticket through the columns, left-to-right, indicates and broadcasts the ticket's progress. The final column, which means the work is done (meaning deployed to production or handed off to another team) is a happy place.&lt;/p&gt;

&lt;p&gt;Good tickets are written so they can be scanned efficiently. They use &lt;a href="https://en.wikipedia.org/wiki/Markdown"&gt;Markdown&lt;/a&gt; to achieve this by marking sections and highlights. Bullet pointed lists are used in abundance. Good tickets also probably have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user story — the requirements from the POV of a user&lt;/li&gt;
&lt;li&gt;Technical requirements if relevant&lt;/li&gt;
&lt;li&gt;Links to other relevant tickets or previous work&lt;/li&gt;
&lt;li&gt;For front-end tickets, links to designs&lt;/li&gt;
&lt;li&gt;Tags! Tags give the team visibility into blockers and future problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I like to keep tickets updated so that stakeholders don't need to chase me for an update. I do this by leaving comments about my progress.&lt;/p&gt;

&lt;p&gt;I've normally been lucky at companies I've worked at in that code reviews are integrated to the issue tracking system. So 'attaching' a pull-request to a ticket is effortless and is achieved with a single click or comment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Document the world
&lt;/h2&gt;

&lt;p&gt;I like documentation. I like the flair that people add. Emojis, GIFs (practical or memes), and embedded videos. I spend a lot of time thinking about the best way to communicate ideas and problems — and I'm very happy that software engineering has allowed me to work on writing for humans. Lean writing that conveys a point. The least words required.&lt;/p&gt;

&lt;p&gt;Some days I will be adding comments to technical designs, or explaining a pull-request that grew in size and requires a lot of context to understand. Other days I'll be breaking down a technology for the wider team by assuming the reader has minimal coding knowledge.&lt;/p&gt;

&lt;p&gt;If you go the extra mile to throw up an internal documentation page after solving a problem, I appreciate you. I see you editing the onboarding documentation, keeping it tight, the instructions clear and friendly, and I thank you.&lt;/p&gt;




&lt;p&gt;Join 300+ people signed up to my &lt;a href="https://buttondown.email/healeycodes"&gt;newsletter&lt;/a&gt; about code and how I write it!&lt;/p&gt;

&lt;p&gt;I tweet about tech &lt;a href="https://twitter.com/healeycodes"&gt;@healeycodes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>career</category>
      <category>programming</category>
      <category>writing</category>
    </item>
    <item>
      <title>Adding Comments to Gatsby with Netlify Serverless Functions + GitHub</title>
      <dc:creator>Andrew Healey</dc:creator>
      <pubDate>Sat, 09 May 2020 09:59:56 +0000</pubDate>
      <link>https://dev.to/healeycodes/adding-comments-to-gatsby-with-netlify-serverless-functions-github-58ch</link>
      <guid>https://dev.to/healeycodes/adding-comments-to-gatsby-with-netlify-serverless-functions-github-58ch</guid>
      <description>&lt;p&gt;I wanted to accept user comments on a Gatsby website and store them on GitHub. As in, I wanted the comments to go straight into a file called &lt;code&gt;comments.json&lt;/code&gt; in my repository. So I could use something as simple as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../comments.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;in my site's code. Without any databases. No third-party plugins making tens of networks requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.netlify.com/functions/overview/"&gt;Netlify serverless functions&lt;/a&gt; allowed me to use GitHub's API to make this repository change with the data from a submitted comment. It also hid my secret API credentials.&lt;/p&gt;

&lt;p&gt;I built a prototype — &lt;a href="https://github.com/healeycodes/gatsby-serverless-comments"&gt;healeycodes/gatsby-serverless-comments&lt;/a&gt; — that uses this flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;👩 User enters a comment and clicks submit.&lt;/li&gt;
&lt;li&gt;⚙️ A serverless function receives the data and hits GitHub's API.&lt;/li&gt;
&lt;li&gt;🔧 It reads the existing &lt;code&gt;comments.json&lt;/code&gt; , appends the new comment, and saves.&lt;/li&gt;
&lt;li&gt;🚧 A new commit triggers a Netlify build.&lt;/li&gt;
&lt;li&gt;✅ The new version of the website is deployed!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The new comment is visible to users ~30 seconds ⏰ after the first click.&lt;/p&gt;

&lt;h2&gt;
  
  
  The serverless function
&lt;/h2&gt;

&lt;p&gt;Let's pick through the serverless function that receives the user's comment. It will make use of some constants that can be set through Netlify's website on &lt;em&gt;settings&lt;/em&gt; → &lt;em&gt;deploys&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sieonf3v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fntg6n5t46tbzqm98zeb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sieonf3v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fntg6n5t46tbzqm98zeb.png" alt="GITHUB_PAT_TOKEN, GITHUB_REPO, GITHUB_USER"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The function is written with &lt;em&gt;Node.js&lt;/em&gt; and exports a &lt;code&gt;handler&lt;/code&gt; function, which is explained in &lt;a href="https://docs.netlify.com/functions/build-with-javascript/#format"&gt;Netlify's documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// comment.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_PAT_TOKEN&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_REPO&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_USER&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.github.com/repos/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="nx"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/contents/src/comments.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Use the Contents API from GitHub&lt;/span&gt;
  &lt;span class="c1"&gt;// https://developer.github.com/v3/repos/contents/#get-contents&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existingFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Pass some kind of authorization&lt;/span&gt;
        &lt;span class="c1"&gt;// I'm using a personal access token&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Basic &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// The file's content is stored in base64 encoding&lt;/span&gt;
  &lt;span class="c1"&gt;// Decode that into utf-8 and then parse into an object&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// This is the user submitted comment&lt;/span&gt;
  &lt;span class="c1"&gt;// Perhaps we would do some validation here&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newComment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Update the comments&lt;/span&gt;
  &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newComment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newComment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newComment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// Use the Contents API to save the changes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Basic &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New comment on &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toDateString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;

      &lt;span class="c1"&gt;// Turn that object back into a string and encoded it&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

      &lt;span class="c1"&gt;// Required: the blob SHA of the existing file&lt;/span&gt;
      &lt;span class="na"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;existingFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

  &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Potential downsides
&lt;/h2&gt;

&lt;p&gt;What if someone spams comments on your website? Well, you'll hit your build time limits pretty fast.&lt;/p&gt;

&lt;p&gt;There's also a small window (10-100s of milliseconds between API calls) where two people comment at the same time and the older comment will be overwritten.&lt;/p&gt;

&lt;p&gt;The fix for both of these is to alter our serverless function to open a pull-request with the comment change. Comments are now delayed but we've protected ourselves from malicious behavior and we can also screen comments for appropriateness. We won't lose any data but might rarely need to handle merge conflicts.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Netlify review
&lt;/h2&gt;

&lt;p&gt;Netlify are betting big on &lt;a href="https://jamstack.org/"&gt;Jamstack&lt;/a&gt; applications. It's a bet I would make too.&lt;/p&gt;

&lt;p&gt;Their developer experience (DX) is up there with the best right now. It's rare I read about a product &lt;em&gt;just working&lt;/em&gt; and then it ends up doing so! Recently, Netlify's snappy deployments let me rush out changes to fix live issues within minutes.&lt;/p&gt;

&lt;p&gt;What does this mean for their future success? Well, Tiny.cloud &lt;a href="https://www.tiny.cloud/blog/developer-experience/"&gt;points out&lt;/a&gt; that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DX is kind of a big deal. Developers can play a huge role in the uptake of your product, especially when you consider they are likely to provide guidance on what tools their organization should invest in, even though the final decision usually happens at the executive level. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Netlify's developer tooling lets me create prototypes like the one you're reading about without having to mess with configuration. &lt;a href="https://healeycodes.com/"&gt;My Gatsby website&lt;/a&gt; is hosted with their generous free tier and transferring and hosting it has been hiccup-free.&lt;/p&gt;

&lt;p&gt;I recommend them.&lt;/p&gt;




&lt;p&gt;Join 300+ people signed up to my &lt;a href="https://buttondown.email/healeycodes"&gt;newsletter&lt;/a&gt; on code and personal growth!&lt;/p&gt;

&lt;p&gt;I tweet about tech &lt;a href="https://twitter.com/healeycodes"&gt;@healeycodes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Hacking Together an E-ink Dashboard</title>
      <dc:creator>Andrew Healey</dc:creator>
      <pubDate>Tue, 28 Apr 2020 20:57:59 +0000</pubDate>
      <link>https://dev.to/healeycodes/hacking-together-an-e-ink-dashboard-1li9</link>
      <guid>https://dev.to/healeycodes/hacking-together-an-e-ink-dashboard-1li9</guid>
      <description>&lt;p&gt;I realized I was asking Google Assistant the same questions over and over again. Like &lt;em&gt;What's the current weather?&lt;/em&gt; or &lt;em&gt;What's on my calendar for today?&lt;/em&gt; So I set out to build a small dashboard for my Raspberry Pi that I could check instead.&lt;/p&gt;

&lt;p&gt;I bought a &lt;em&gt;Waveshare 2.7inch e-Paper HAT (B)&lt;/em&gt; for ~$20. This version comes with a printed circuit board that slots right onto the general-purpose input/output (GPIO) pins of a Raspberry Pi. You can also use the included wires to avoid blocking the unused GPIO pins.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AyYgziZp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4kjvmvku6dclbpdmwmsh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AyYgziZp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4kjvmvku6dclbpdmwmsh.jpg" alt="e-Paper PCB, manual, GPIO wires, screws"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I followed the installation instructions from the &lt;a href="https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT_(B)"&gt;Waveshare wiki&lt;/a&gt; and fired up one of the hello world programs — there are &lt;a href="https://github.com/waveshare/e-Paper"&gt;versions in C and Python&lt;/a&gt;. The steps to get this going, as well as the example programs, are quite accessible compared to other Raspberry Pi hardware accessories.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3L4aU87u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mhgn5plugk98a6gkb4tj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3L4aU87u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/mhgn5plugk98a6gkb4tj.jpg" alt="Hello world program, sample text and images on the screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I started picking through the source code of the Python examples to understand how &lt;a href="https://pypi.org/project/epd-library/"&gt;epd-library&lt;/a&gt; works. The version &lt;em&gt;B&lt;/em&gt; of this screen is tri-colored, it can display white, black, and red, and the resolution is 264x176. The examples start by clearing the screen (filling it with white) and then create two layers which are &lt;a href="https://pillow.readthedocs.io/en/stable/reference/Image.html"&gt;Image&lt;/a&gt; objects from the &lt;a href="https://pillow.readthedocs.io/en/stable/"&gt;Pillow library&lt;/a&gt;. One of the layers is the black content and the other layer is the red content. These are then passed to the render function &lt;code&gt;EPD::display&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I've extracted and commented the key parts of &lt;code&gt;examples/epd_2in7b_test.py&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# initialize the EPD class
&lt;/span&gt;&lt;span class="n"&gt;epd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;epd2in7b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EPD&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# start with a clear (white) background
&lt;/span&gt;&lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# define a simple font
&lt;/span&gt;&lt;span class="n"&gt;font24&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;truetype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;picdir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Font.ttc'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# create empty image objects/layers
&lt;/span&gt;&lt;span class="n"&gt;HBlackimage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;HRedimage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# render the black section
&lt;/span&gt;&lt;span class="n"&gt;black_draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HBlackImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;black_draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;top_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONT_LARGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# calculate the depth of the black section
&lt;/span&gt;&lt;span class="n"&gt;top_depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;black_draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textsize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONT_LARGER&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;

&lt;span class="c1"&gt;# render the red section under the black section
&lt;/span&gt;&lt;span class="n"&gt;red_draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HRedImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;red_draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_depth&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;bottom_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONT_SMALLER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getbuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HBlackImage&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getbuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HRedImage&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;For the prototype, I'll show the current weather and some news headlines from the BBC. &lt;a href="https://openweathermap.org/"&gt;Open Weather&lt;/a&gt; and &lt;a href="https://newsapi.org/"&gt;News API&lt;/a&gt; both have generous free-tiers. For each of these features, I'll write functions that return text which I can create layers from.&lt;/p&gt;

&lt;p&gt;I'll be using &lt;a href="https://docs.python.org/3.5/library/stdtypes.html#str.format"&gt;formatted strings&lt;/a&gt; from Python 3.5 (as opposed to the superior &lt;a href="https://docs.python.org/3/whatsnew/3.6.html#whatsnew36-pep498"&gt;f-strings&lt;/a&gt;) because that's what &lt;a href="https://www.raspberrypi.org/downloads/raspbian/"&gt;Raspian&lt;/a&gt; (the default Raspberry Pi operating system) comes with.&lt;/p&gt;

&lt;p&gt;This function talks to Open Weather using &lt;a href="https://pypi.org/project/pyowm/"&gt;PyOWM&lt;/a&gt;, a wrapper library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="s"&gt;'''
    Get the formatted weather description.
    E.g. `Richmond, GB: Clear sky`
    '''&lt;/span&gt;
    &lt;span class="n"&gt;owm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyowm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OWM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OPEN_WEATHER_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;observation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;owm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weather_at_place&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOCATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;detailed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_detailed_status&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;capitalize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;temp_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'celsius'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;current_temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"temp"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;min_temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"temp_min"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;max_temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"temp_max"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'{}: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;{}°C ({}°C - {}°C)'&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOCATION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detailed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_temp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;News API sadly doesn't have an up-to-date library available so I use &lt;a href="https://pypi.org/project/requests/"&gt;requests&lt;/a&gt; to make an HTTP call and parse the data. I can display about three headlines (including an extra wrapped line for each). The text needs to be manually &lt;a href="https://en.wikipedia.org/wiki/Line_wrap_and_word_wrap"&gt;wrapped&lt;/a&gt; to stop it going off-screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_news&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;'''
    Get some news headlines from the BBC formatted in a list.
    E.g. `- Acclaimed Swedish author Per Olov Enquist dies
    - PM's return to work 'a boost for the country'
    - 'Myth that Sweden has not taken serious steps'`
    '''&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'https://newsapi.org/v2/top-headlines?sources=bbc-news&amp;amp;apiKey={}'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NEWS_API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'articles'&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;

    &lt;span class="n"&gt;wrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textwrap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;headline&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;'- {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headline&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;'{}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With these two sections of text, I create the image layers. I want the weather to be on the black-colored layer with a slightly larger font. The news will be underneath on the red-colored layer. Here's a function that takes an instance of the EPD class as well as two sections of text.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bottom_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;'''
    Given an EPD instance, and sections of text, render the text.
    '''&lt;/span&gt;
    &lt;span class="n"&gt;HBlackImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epd2in7b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EPD_HEIGHT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;epd2in7b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EPD_WIDTH&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;HRedImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epd2in7b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EPD_HEIGHT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;epd2in7b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EPD_WIDTH&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# render the black section
&lt;/span&gt;    &lt;span class="n"&gt;black_draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HBlackImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;black_draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;top_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONT_LARGER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# calculate the depth of the black section
&lt;/span&gt;    &lt;span class="n"&gt;top_depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;black_draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textsize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONT_LARGER&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;

    &lt;span class="c1"&gt;# render the red section under the black section
&lt;/span&gt;    &lt;span class="n"&gt;red_draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HRedImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;red_draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_depth&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;bottom_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FONT_SMALLER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getbuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HBlackImage&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getbuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HRedImage&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The two Image objects contain simple data and don't 'know' what color they are. They look like this when rendered to bitmap format.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sSN6WThW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xm28808tcjpqgsx63p8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sSN6WThW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xm28808tcjpqgsx63p8f.png" alt="A weather status"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice the space that the second layer leaves for the first layer so that the text doesn't overlap.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Xxozu_SV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n2k9fxhtfsqoncucg6o1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Xxozu_SV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/n2k9fxhtfsqoncucg6o1.png" alt="News headlines"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main function of this program makes sure that we only display the dashboard during hours when someone will be awake. I want to avoid &lt;a href="https://en.wikipedia.org/wiki/Screen_burn-in"&gt;ghosting&lt;/a&gt; which the documentation warns about. The EPD class that gets passed to the &lt;code&gt;show_text&lt;/code&gt; function is initialized here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;epd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;epd2in7b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EPD&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Init and Clear"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


        &lt;span class="n"&gt;show_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;bottom_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_news&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="c1"&gt;# leave the dashboard up for half an hour
&lt;/span&gt;        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Clear..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;epd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;IOError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ctrl + c:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;epd2in7b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;epdconfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;module_exit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# rest the screen outside this range
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# update the dashboard
&lt;/span&gt;        &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Let's see it in action!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BAIlSrVb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ezsijpzc5ve0ocfjzzn9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BAIlSrVb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/ezsijpzc5ve0ocfjzzn9.jpg" alt="E-ink screen rendering both sections of text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm not sure if I'll flesh this prototype out into an open source project so for now the full source code is in a &lt;a href="https://gist.github.com/healeycodes/38c256c747eac46ac4ffc881e93bf095"&gt;gist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;I didn't invest any time in working with the four buttons on the left of the screen (they're labelled KEY1, KEY2, etc. in the above picture) but I think it would be worthwhile to integrate them in a fleshed out dashboard — they connect as standard GPIO buttons.&lt;/p&gt;

&lt;p&gt;I've learned that e-ink screens are worth the investment for me and I might look into getting a much larger version. The viewing angle is like that of a &lt;a href="https://en.wikipedia.org/wiki/Amazon_Kindle"&gt;Kindle&lt;/a&gt; and although it's small it's beautiful to look at and has a more 'real' feel than a laptop or smartwatch screen.&lt;/p&gt;

&lt;p&gt;I look forward to seeing this technology progress and the commercial applications that arise. I saw that Whole Foods Market were using them in-store back &lt;a href="https://web.archive.org/web/20170107033856/http://www.pamplinmedia.com/lor/48-news/314796-193219-thoughtfully-simple"&gt;in 2016&lt;/a&gt;. It would be nice to have less LEDs staring at me whenever I leave the house!&lt;/p&gt;




&lt;p&gt;Join 250+ people signed up to my &lt;a href="https://buttondown.email/healeycodes"&gt;newsletter&lt;/a&gt; on programming and personal growth!&lt;/p&gt;

&lt;p&gt;I tweet about code &lt;a href="https://twitter.com/healeycodes"&gt;@healeycodes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>linux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I'm Working Remotely</title>
      <dc:creator>Andrew Healey</dc:creator>
      <pubDate>Sun, 26 Apr 2020 15:04:58 +0000</pubDate>
      <link>https://dev.to/healeycodes/how-i-m-working-remotely-50mm</link>
      <guid>https://dev.to/healeycodes/how-i-m-working-remotely-50mm</guid>
      <description>&lt;p&gt;Madeline and I moved our living room around so our desks could be up against the blinds in the lightest area. We have our full work setups here. We carried the gear home in a duffle bag and went back a week later for the &lt;a href="https://en.wikipedia.org/wiki/Aeron_chair"&gt;chairs&lt;/a&gt; which, while being amongst the most ergonomic objects I've ever touched, rattled like heck as we wheeled them home.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--owN1t_a6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gm30vj5nn2iyh5g4dmyo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--owN1t_a6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gm30vj5nn2iyh5g4dmyo.jpg" alt="Our living room, a sofa, rug, long desk, two setups"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we have meetings at the same time, one of us normally goes into another room. It depends on how much we're speaking. When we're both talking we tend to get louder and louder, without realizing it, until we're almost shouting.&lt;/p&gt;

&lt;p&gt;This is where I listen to lofi hip hop streams and build software and record GIFs to make my pull requests all pretty. My MacBook Pro 2019 is paired with a small £12 bluetooth keyboard because my work keyboard won't fit comfortably on the kitchen table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1AHi2U4S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1m9sl3ewvzwqkrf5il70.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1AHi2U4S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/1m9sl3ewvzwqkrf5il70.png" alt="Laptop, a small keyboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last week, I was in the other room and Madeline pranked me by turning the brightness and volume up and down on my MacBook via this keyboard (which I had left behind). I was in a casual call with someone at the time and I was describing, while bewildered, all of the weird symptoms until I heard Madeline stifling laughter on the other side of the door.&lt;/p&gt;

&lt;p&gt;I spend a lot of time on Slack, Google Hangouts, and Discord. On Slack, my team has a private team channel for work memes and things that relate to the &lt;a href="https://en.wikipedia.org/wiki/Scrum_Sprint"&gt;sprint&lt;/a&gt;. I find that when working remote, it's best to have conversations in channels rather than private messages because this shares knowledge and context among the team. Messages in channels are also searchable by the team (even though Slack's search feature leaves much to be desired). I found that it was worth learning a few Slack shortcuts to increase my productivity e.g. CMD+K to quickly navigate, UP ARROW to edit your last message.&lt;/p&gt;

&lt;p&gt;We use Hangouts for our regularly scheduled meetings, all synced by Google Calendar. Hangouts has slightly worse quality than Zoom and seems to use up more battery. Discord has been great for pair programming. The stream feature is being improved almost every day. Discord is better for when I'm working closely with someone on a feature but we only need each other every few minutes. In between these moments we can go silent or just idly chat.&lt;/p&gt;

&lt;p&gt;I've been quite into videos from a train driver's point of view. They're long and uneventful and beautiful. I leave these 12hr streams on my third screen. These videos are often from Norway, where there is the &lt;a href="https://en.wikipedia.org/wiki/Slow_television"&gt;slow television&lt;/a&gt; movement.&lt;/p&gt;

&lt;p&gt;The usual meeting schedule, and the cadence of our sprints, is the same as before we all went remote. Before, we each worked 1-2 days remotely each week so the transition hasn't been too bad. I have been trying to be positive and I've been looking forward to reviewing our remote practices for when we get back to the office. It's always been a priority for me to make sure that anyone remote is included fully in discussions and activities at the office.&lt;/p&gt;

&lt;p&gt;One trick for connecting the remote person to office life is to let them lead the daily standup while they share their screen (usually viewing the sprint board). This ensures that the remote person understands everything that is going on and feels comfortable asking for clarifications. It ensures that everyone is conscious of the microphone setup at the office too (for when there is one shared microphone e.g. a standup meeting). Alternatively, everyone at the office can dial into the meeting separately to make it a level playing ground.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6njsc4kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lul00azl3anvpwtp2oul.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6njsc4kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lul00azl3anvpwtp2oul.jpg" alt="Richmond Park"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At lunch Madeline and I usually go for runs in Richmond Park. We work hard to keep two meters+ away from people. It's been beautiful to watch the season change into Summer.&lt;/p&gt;

&lt;p&gt;I have been drinking more coffee — cold brew and instant — especially in the mornings. We discovered the joy of barista-style oat milk (shaking the carton plenty before pouring) and it has ruined all other types of oat milk for us! It's apparently made from liquid oats (I looked it up but still don't know what it means) which keeps the milk from separating when the coffee is left still.&lt;/p&gt;

&lt;p&gt;Deploying code has been scarier while remote. At the office there is always someone around to help with problems even though the way of grabbing them is the same (we message on Slack if someone's headphones are in). However, the only problem I've run into is GitHub being down, which can affect our CI/CD systems. I'm lucky that Madeline works at the same company as me. She's in a different team but we have been sneaking in some pairing sessions and helping each other out. I've learned more about the area that her team works in. Our teams sit a few meters away at the office but our processes are surprisingly different. It's been fun to learn what works for them and then to take it back to my team. Madeline and I have been speeding up each other's features by calling out questions across the kitchen table when we're stuck.&lt;/p&gt;

&lt;p&gt;A junior co-worker told me that they are googling more and getting less stuck because they aren't embarrassed by the idea of other people looking at their screen. I told them that I sometimes barely understand what is happening on my screen let alone anyone else's screen in the vicinity. They also told me they are learning quicker because they are seeking out help less.&lt;/p&gt;

&lt;p&gt;My work tickets lately have been all over the stack, touching a little bit of everything. It's been fun and keeps things fresh. I hope soon to be able to deep dive into a technology like I did a few months ago when we shipped a Vue.js prototype.&lt;/p&gt;

&lt;p&gt;We have a moodboard at the office — a big whiteboard off in one corner with a grid of days and people's names. We draw pictures, or write words, to describe our days so we can look back in &lt;a href="https://en.wikipedia.org/wiki/Scrum_(software_development)#Sprint_retrospective"&gt;retrospective meetings&lt;/a&gt; (once per two-week sprint). Before, when it was rarer to be remote, we sometimes had people describe their moodboard drawings for someone else to quickly sketch up which I enjoyed doing even when my doodles came out goofy. Now, we have moved to a fully remote moodboard with Miro. Which means we can use GIFs! On Miro, you can also see ghost-like cursors, belonging to faraway engineers, move around the shared board.&lt;/p&gt;

&lt;p&gt;Our plant growing has extended to things that are consumable. With garlic, onion and spring onion, as well as pak choy, we hope to grow enough to make one meal within lockdown! It is meditative to care for them. I have lost count of the things that are growing in this apartment. And I'm thankful that Madeline is the main waterer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lGzaCj_x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8skxlnwjnbgronqhqbmt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lGzaCj_x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8skxlnwjnbgronqhqbmt.jpg" alt="Potted cloves of garlic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The bi-monthly hackday was held remotely and Madeline and I spun up a &lt;em&gt;QUAKE III: Team Arena&lt;/em&gt; server from scratch and deployed it so that we can use it for the #gaming Slack channel at work. We used to have the server hosted on someone's laptop. Our humorous presentation was about client/server latency and why the person hosting the game had an unfair advantage. We also started building a Vue.js app to help us keep track of goods around the house that we buy on different schedules e.g. kitchen roll, and black bin bags but ran out of time to fully flesh it out.&lt;/p&gt;

&lt;p&gt;I've been reading more in the evenings. I bought The Electric State by Simon Stålenhag after admiring his art on Twitter. I've been reading articles from &lt;a href="https://lobste.rs/"&gt;Lobsters&lt;/a&gt;, &lt;a href="https://news.ycombinator.com/"&gt;Hacker News&lt;/a&gt;, as well as The New Yorker — I really liked &lt;a href="https://www.newyorker.com/magazine/2018/12/10/the-friendship-that-made-google-huge"&gt;The Friendship That Made Google Huge&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hope y'all are staying safe 👋&lt;/p&gt;




&lt;p&gt;Join 250+ people signed up to my &lt;a href="https://buttondown.email/healeycodes"&gt;newsletter&lt;/a&gt; on programming and personal growth!&lt;/p&gt;

&lt;p&gt;I tweet about code &lt;a href="https://twitter.com/healeycodes"&gt;@healeycodes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>career</category>
      <category>remote</category>
    </item>
    <item>
      <title>Twitter Ticker Tape with a POS58 Receipt Printer</title>
      <dc:creator>Andrew Healey</dc:creator>
      <pubDate>Sat, 11 Apr 2020 09:16:23 +0000</pubDate>
      <link>https://dev.to/healeycodes/twitter-ticker-tape-with-a-pos58-receipt-printer-lpe</link>
      <guid>https://dev.to/healeycodes/twitter-ticker-tape-with-a-pos58-receipt-printer-lpe</guid>
      <description>&lt;p&gt;Screens tend to keep me awake in the evening so I wondered if I could print out my Twitter home timeline, live, as I read a book in the evening.&lt;/p&gt;

&lt;p&gt;Here's the final project in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqk4sk9jgoarosa4a97d5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fqk4sk9jgoarosa4a97d5.gif" alt="Ticket tape in action, a receipt printer printing out textual tweets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was gifted a receipt printer for Christmas to use with my Raspberry Pi. A &lt;em&gt;POS58 USB Thermal Receipt Printer&lt;/em&gt; also known as a &lt;em&gt;ZJ-5890K&lt;/em&gt;. It's sold by a few different brands under different names.&lt;/p&gt;

&lt;p&gt;I couldn't get the drivers to work on Linux, MacOS, or Windows. I was stuck until I found &lt;a href="https://github.com/vpatron/usb_receipt_printer" rel="noopener noreferrer"&gt;vpatron/usb_receipt_printer&lt;/a&gt; — a guide that walks through setting the device up on a Raspberry Pi. It shows how to write to the USB port directly (a tactic I previously tried and failed). The included demo program solved all of my printing problems. Thanks Vince Patron!&lt;/p&gt;

&lt;h2&gt;
  
  
  Tweepy
&lt;/h2&gt;

&lt;p&gt;Twitter has a powerful API but it's a little cryptic at first glance. Tweepy is a Python library for accessing the Twitter API — it has good documentation and community support.&lt;/p&gt;

&lt;p&gt;In order to use Tweepy, we need to create an application on Twitter's Developer platform to get the keys and tokens we need to authenticate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flr4w6p1i28xbdeo1z3gs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Flr4w6p1i28xbdeo1z3gs.png" alt="Keys, secret keys and access token management page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will have three files in total.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;config.py&lt;/code&gt; — for managing our keys.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;poller.py&lt;/code&gt; — for polling Twitter's API via Tweepy, and converting tweets to text.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;posprint.py&lt;/code&gt; — for calling the receipt printer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We save the keys in a separate file. A better way of managing keys is for your application to access them at runtime through &lt;a href="https://en.wikipedia.org/wiki/Environment_variable" rel="noopener noreferrer"&gt;environment values&lt;/a&gt;. This can be safer (it avoids keys accidentally being pushed to GitHub) and lets our application use different keys in different environments (e.g. local versus live production deployment).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config.py
&lt;/span&gt;
&lt;span class="n"&gt;consumer_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;consumer_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;access_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;access_token_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twitter has a streaming data API but it was harder to set up than the chosen solution.&lt;/p&gt;

&lt;p&gt;We ask for the latest 20 tweets every minute and check that we haven't seen them before. Then we format, clean, and print the new ones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# poller.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tweepy&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;emoji&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;demojize&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OrderedDict&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;posprint&lt;/span&gt;

&lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tweepy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;OAuthHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;consumer_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;consumer_secret&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_access_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;access_token_secret&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tweepy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;API&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# we use an ordered dictionary so we can remove old tweets
# from the data structure. Our program may be long-running
# and we don't want to run out of memory
&lt;/span&gt;&lt;span class="n"&gt;seen_statuses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderedDict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;public_tweets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;home_timeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweet_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;extended&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;public_tweets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

        &lt;span class="c1"&gt;# we're interested in statuses we haven't seen before
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen_statuses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

            &lt;span class="c1"&gt;# store the statuses we use
&lt;/span&gt;            &lt;span class="n"&gt;seen_statuses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;

            &lt;span class="c1"&gt;# dump old statuses that can't appear again
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seen_statuses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;seen_statuses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popitem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;screen_name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

            &lt;span class="c1"&gt;# get the full tweet text regardless of type
&lt;/span&gt;            &lt;span class="c1"&gt;# https://github.com/tweepy/tweepy/issues/935
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;retweeted_status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;tweet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retweeted_status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;full_text&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;tweet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;full_text&lt;/span&gt;

            &lt;span class="c1"&gt;# trim unprintable characters that POS58 can't output
&lt;/span&gt;            &lt;span class="c1"&gt;# by turning emojis into textual representation.
&lt;/span&gt;            &lt;span class="c1"&gt;# 😅 becomes :grinning_face_with_sweat:
&lt;/span&gt;            &lt;span class="n"&gt;tweet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;demojize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ascii&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="c1"&gt;# throwaway non ascii characters
&lt;/span&gt;                &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ignore&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;posprint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tweet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# twitter restricts statuses/home_timeline to once per minute
&lt;/span&gt;    &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's modify Vince's demo program &lt;a href="https://github.com/vpatron/usb_receipt_printer" rel="noopener noreferrer"&gt;vpatron/usb_receipt_printer&lt;/a&gt; to export the function we call above — &lt;code&gt;posprint.output(tweet)&lt;/code&gt; — with our textual tweet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# posprint.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;usb.core&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;usb.util&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;textwrap&lt;/span&gt;

&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Demo program to print to the POS58 USB thermal receipt printer. This is
labeled under different companies, but is made by Zijiang. See
http://zijiang.com

MIT License — Copyright (c) 2019 Vince Patron
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="c1"&gt;# In Linux, you must:
#
# 1) Add your user to the Linux group "lp" (line printer), otherwise you will
#    get a user permissions error when trying to print.
#
# 2) Add a udev rule to allow all users to use this USB device, otherwise you
#    will get a permissions error also. Example:
#
#    In /etc/udev/rules.d create a file ending in .rules, such as
#    33-receipt-printer.rules with the contents:
#
#   # Set permissions to let anyone use the thermal receipt printer
#   SUBSYSTEM=="usb", ATTR{idVendor}=="0416", ATTR{idProduct}=="5011", MODE="666"
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# find our device
&lt;/span&gt;    &lt;span class="c1"&gt;# 0416:5011 is POS58 USB thermal receipt printer
&lt;/span&gt;    &lt;span class="n"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;usb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idVendor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mh"&gt;0x0416&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idProduct&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mh"&gt;0x5011&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# was it found?
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dev&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Device not found&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# disconnect it from kernel
&lt;/span&gt;    &lt;span class="n"&gt;needs_reattach&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_kernel_driver_active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;needs_reattach&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detach_kernel_driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# set the active configuration. With no arguments, the first
&lt;/span&gt;    &lt;span class="c1"&gt;# configuration will be the active one
&lt;/span&gt;    &lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_configuration&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# get an endpoint instance
&lt;/span&gt;    &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_active_configuration&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;intf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

    &lt;span class="n"&gt;ep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;usb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_descriptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;intf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# match the first OUT endpoint
&lt;/span&gt;        &lt;span class="n"&gt;custom_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; \
        &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; \
            &lt;span class="n"&gt;usb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endpoint_direction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bEndpointAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; \
            &lt;span class="n"&gt;usb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ENDPOINT_OUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;ep&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="c1"&gt;# print!
&lt;/span&gt;    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textwrap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n\n\n\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# reattach if it was attached originally
&lt;/span&gt;    &lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;needs_reattach&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach_kernel_driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Reattached USB device to kernel driver&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our program is started by running &lt;code&gt;poller.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If everything went correctly, our POS58 printer should output tweets as they arrive in the following width-restricted format which avoids breaking words where possible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jessfraz: Ive started using
Pocket to keep articles I want
to read later and also
combined it with a bot to read
RSS feeds. Its alright so far.
What are some tools like this
you cant live without?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks again to &lt;a href="https://github.com/vpatron" rel="noopener noreferrer"&gt;Vince Patron&lt;/a&gt; for his comprehensive guide!&lt;/p&gt;




&lt;p&gt;Join 250+ people signed up to my &lt;a href="https://buttondown.email/healeycodes" rel="noopener noreferrer"&gt;newsletter&lt;/a&gt; on programming and personal growth!&lt;/p&gt;

&lt;p&gt;I tweet about code &lt;a href="https://twitter.com/healeycodes" rel="noopener noreferrer"&gt;@healeycodes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>linux</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Book Review: The Developer's Guide to Content Creation</title>
      <dc:creator>Andrew Healey</dc:creator>
      <pubDate>Mon, 09 Mar 2020 21:25:01 +0000</pubDate>
      <link>https://dev.to/healeycodes/book-review-the-developer-s-guide-to-content-creation-4hkg</link>
      <guid>https://dev.to/healeycodes/book-review-the-developer-s-guide-to-content-creation-4hkg</guid>
      <description>&lt;p&gt;I'm in year two of technical blogging and I've learned a lot. I have far &lt;em&gt;far&lt;/em&gt; more views and comments than I thought I would at this point. At the beginning I was throwing my thoughts into the void and it felt good. Reading &lt;a href="https://www.developersguidetocontent.com/"&gt;&lt;strong&gt;The Developer's Guide to Content Creation&lt;/strong&gt;&lt;/a&gt; reminded me of those roots. It inspired me.&lt;/p&gt;

&lt;p&gt;I love seeing people blog about their passions and thoughts especially when it's on a personal website. Personal websites are cool and weird and make the internet more enjoyable for me. If there's a link in your bio there's a high chance I'll click it.&lt;/p&gt;

&lt;p&gt;So I'm very happy, on this quiet and unusually cold Sunday, that there are resources for those who are starting out on this journey!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Best Beginner Introduction to Blogging
&lt;/h2&gt;

&lt;p&gt;Without a doubt &lt;a href="https://www.developersguidetocontent.com/"&gt;The Developer's Guide to Content Creation&lt;/a&gt; is the best resource for those looking to start writing content for developers. Before reading it, I had a shortlist of topics that I thought such a resource should cover and it nailed every one. Morillo's writing is clear and engaging. She tells us how to write content that is accessability in presentation but also in content. She reminds us how not to write as well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"Just follow these simple steps"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"It’s easy to put together"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"The answer is obvious"&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;I’ve realized that many writers of technical content use these words to make the subject matter seem less intimidating to the reader. They genuinely want to remove friction for their users, but they’re using language that makes the reader feel like the problem is with them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Rich in Resources
&lt;/h2&gt;

&lt;p&gt;The Developer's Guide to Content Creation also contains copious amounts of worksheets to inspire you, help you strategize, and put together a content calendar.&lt;/p&gt;

&lt;p&gt;There are resources linked throughout the book that compliment each section. There's also an example of &lt;em&gt;how&lt;/em&gt; to insert resources into an article in a more descriptive manner. To mirror one of Morillo's examples, take GitHub's trending page.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"GitHub has a list of trending repositories. Check it out &lt;a href="https://github.com/trending"&gt;here&lt;/a&gt;."&lt;/li&gt;
&lt;li&gt;"&lt;a href="https://github.com/trending"&gt;Search through GitHub's list of trending repositories &lt;/a&gt;."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a reader, the second version flows better.&lt;/p&gt;

&lt;p&gt;Morillo recommends comprehensive resources like &lt;a href="https://www.digitalocean.com/community/tutorials/digitalocean-s-technical-writing-guidelines"&gt;DigitalOcean's Technical Writing Guidelines&lt;/a&gt; as well as tools that take seconds to set up and run on your writing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://alexjs.com/"&gt;AlexJS&lt;/a&gt; is an open-source tool that will help you "spot insensitive, inconsiderate writing."&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.hemingwayapp.com/"&gt;Hemingway App&lt;/a&gt; is an online editor that helps you simplify your writing. It highlights confusing sections, areas that are too long, and more.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To add my own, reading other technical blogs can be a good resource and I'd recommend Hacker Noon's &lt;a href="https://hackernoon.com/personal-developer-blog-of-the-year-hacker-noon-noonies-awards-2019-hz2tu32ql"&gt;shortlist for Personal Developer Blog of the Year&lt;/a&gt; to get started. Drew DeVault posts &lt;a href="https://drewdevault.com/"&gt;great content&lt;/a&gt; and &lt;a href="https://drewdevault.com/make-a-blog"&gt;will occasionally pay out $20&lt;/a&gt; to people starting a technical blog on an open platform (i.e. not Medium). Google have also released a free &lt;a href="https://developers.google.com/tech-writing"&gt;course on technical writing&lt;/a&gt; although I believe it's aimed more towards those writing technical documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shape of a Post
&lt;/h2&gt;

&lt;p&gt;Morillo talks a lot about how technical blogs are consumed and how they appear to users.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before you start writing, you need to understand how people read online. People don’t read every word and every sentence; they scan. They’re scanning for the most relevant information and things that pop out at them will command their attention.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thinking about the shape of a post can help during the editing stage. It's a way to cut through complexity and grab readers who would have otherwise failed to skim read blocks of text and given up. It also helps those who come to your post only looking for that one line of code that will unblock them.&lt;/p&gt;

&lt;p&gt;We're reminded that people scan for "keywords, section headings, bullets, anchor text (when hyperlinked), words that are bolded or italicized for emphasis".&lt;/p&gt;



&lt;p&gt;The Developer's Guide to Content Creation covers topics that were getting dusty in the back of my mind as well as delivering a nuggets of wisdom. Even as I write this post, I'm considering Morillo's advice. It's for this reason that I would recommend this book to anyone writing content for developers no matter their experience. It has changed the way I will approach writing for my personal blog as well as for my company's blog.&lt;/p&gt;

&lt;p&gt;Following this book, Morillo is currently writing &lt;a href="https://www.developersguidetocontent.com/book-publishing-guide"&gt;The Developer's Guide to Book Publishing&lt;/a&gt; — I'm looking forward to it!&lt;/p&gt;




&lt;p&gt;Join 200+ people signed up to my &lt;a href="https://buttondown.email/healeycodes"&gt;newsletter&lt;/a&gt; on programming and personal growth!&lt;/p&gt;

&lt;p&gt;I tweet about code &lt;a href="https://twitter.com/healeycodes"&gt;@healeycodes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>books</category>
      <category>news</category>
      <category>writing</category>
      <category>blog</category>
    </item>
    <item>
      <title>DEV Article Analysis</title>
      <dc:creator>Andrew Healey</dc:creator>
      <pubDate>Sun, 19 Jan 2020 16:33:08 +0000</pubDate>
      <link>https://dev.to/healeycodes/dev-article-analysis-2d40</link>
      <guid>https://dev.to/healeycodes/dev-article-analysis-2d40</guid>
      <description>&lt;p&gt;My second passion alongside coding is writing. Whenever I can combine them, I do. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XBrCzveP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/feoe1skq3szuevohcyi9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XBrCzveP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/feoe1skq3szuevohcyi9.gif" alt="The analysis tool in action"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This weekend I used the &lt;a href="https://docs.dev.to/api/"&gt;DEV API&lt;/a&gt; to build &lt;a href="https://dev-article-analysis.glitch.me/"&gt;a tool&lt;/a&gt; that analyzes your articles over time. It's an interactive website where you can enter DEV usernames and get text statistics shown in pretty graphs. The metrics that I chose were reading level, ease of reading, and sentiment analysis.&lt;/p&gt;

&lt;p&gt;It's open-sourced at &lt;a href="https://github.com/healeycodes/dev-article-analysis"&gt;healeycodes/dev-article-analysis&lt;/a&gt;!&lt;/p&gt;

&lt;h3&gt;
  
  
  Readability
&lt;/h3&gt;

&lt;p&gt;The Flesch–Kincaid readability tests were developed in the U.S. Navy in the 70s. These scales are used to measure and compare books, newspapers, and articles. In Florida, they are used to encourage readable language in insurance policies.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Florida Statutes Section 627.4145 (1) (a) The text achieves a minimum score of 45 on the Flesch reading ease test as computed in subsection (5) or an equivalent score on any other test comparable in result and approved by the office;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Flesch-Kincaid Grade scores text at a U.S. grade level. A score of 10.4 means that a tenth grader would be able to understand the article.&lt;/p&gt;

&lt;p&gt;Flesch reading ease is a similar test with different weightings. It results in a score generally ranging between 0-100. Higher means easier to read. Wikipedia has a &lt;a href="https://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests#Flesch_reading_ease"&gt;table&lt;/a&gt; which relates score to grade level with some notes.&lt;/p&gt;

&lt;p&gt;Technical articles, especially those with code excerpts, get wide-ranging scores. However, a user's writing style (e.g. a similar amount of code per article) means that a reasonable line can be drawn over time, perhaps leading to some basic conclusions. &lt;a href="https://www.npmjs.com/package/text-readability"&gt;text-readability&lt;/a&gt; was used to calculate these scores in the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sentiment
&lt;/h3&gt;

&lt;p&gt;Sentiment analysis helps us understand the emotional polarity of a text. For example, the phrase &lt;code&gt;I love cats, but I am allergic to them.&lt;/code&gt; gives a comparative score of &lt;code&gt;0.1111111111111111&lt;/code&gt;. To calculate this, a list of words (and emojis) are given weightings. -5 to 5, positive to negative. The only tokens in our sentence with scores are &lt;code&gt;{ allergic: -2 }, { love: 3 }&lt;/code&gt;. We take the number of tokens, &lt;code&gt;9&lt;/code&gt;, and calculate &lt;code&gt;(3 + -2) / 9&lt;/code&gt; to find the comparative score. This example is used in the documentation of &lt;a href="https://www.npmjs.com/package/sentiment"&gt;sentiment&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The DEV API provides the text of an article in HTML or markdown, neither of which is friendly to analysis. This is why &lt;a href="https://www.npmjs.com/package/remove-markdown"&gt;remove-markdown&lt;/a&gt; is used to reduce the &lt;code&gt;body_markdown&lt;/code&gt; value to plaintext.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interaction
&lt;/h3&gt;

&lt;p&gt;Users enter a DEV username. As the articles are retrieved, a progress status (and a random positive emoji) are sent to the page via WebSocket. This means the user isn't left sitting looking at a spinner.&lt;/p&gt;

&lt;p&gt;I normally use Chart.js to render basic line charts but wondered if there were a simpler solution. I tried ApexCharts.js and didn't run into any problems. The online documentation is good and I searched and found the solution to one of my errors in a GitHub Issue. However, I might as well have used Chart.js because the level of customization I required meant the syntax for both libraries was near-identical.&lt;/p&gt;

&lt;p&gt;The charts can be hovered to see article titles and their scores for each metric.&lt;/p&gt;

&lt;p&gt;When arriving at the website, the graphs are already filled with precalculated data from my own DEV username (healeycodes). This is to help visitors better understand what the end result of their interaction will be.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;p&gt;I developed this Node/Express application on &lt;a href="https://glitch.com/"&gt;Glitch&lt;/a&gt;. Their cloud IDE and console lets me move fast and get feedback from people seconds after I've made a change. You can export the project to a GitHub project (it creates a &lt;code&gt;glitch&lt;/code&gt; branch which you can open a pull request from).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6FtnasnQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/nxxcg4sbq7bknk3d0ta5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6FtnasnQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/nxxcg4sbq7bknk3d0ta5.png" alt="Glitch cloud IDE"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes I want to upload image files and in the past I found this tricky. Glitch wants you to use their asset mechanism for this, which makes exporting the whole project to GitHub harder than it needs to be. My workaround is to upload the image to the project using the asset mechanism, copy the long URL they provide, &lt;code&gt;wget&lt;/code&gt; this URL in the console which downloads it into the project, and finally using &lt;code&gt;mv&lt;/code&gt; to change the name of the image.&lt;/p&gt;

&lt;p&gt;This often brings the cloud IDE out of sync but entering &lt;code&gt;refresh&lt;/code&gt; in console fixes this.&lt;/p&gt;




&lt;p&gt;Join 200+ people signed up to my &lt;a href="https://buttondown.email/healeycodes"&gt;newsletter&lt;/a&gt; on programming and personal growth!&lt;/p&gt;

&lt;p&gt;I tweet about code &lt;a href="https://twitter.com/healeycodes"&gt;@healeycodes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>writing</category>
    </item>
    <item>
      <title>Gatsby Sparks Joy</title>
      <dc:creator>Andrew Healey</dc:creator>
      <pubDate>Sun, 12 Jan 2020 10:37:07 +0000</pubDate>
      <link>https://dev.to/healeycodes/gatsby-sparks-joy-409k</link>
      <guid>https://dev.to/healeycodes/gatsby-sparks-joy-409k</guid>
      <description>&lt;p&gt;I migrated from Jekyll to Gatsby recently and so far I've had a really neat time. The whole process took about a week of casual coding (a few hours here and there). The Gatsby ecosystem enabled me to quickly add a few features to my blog that I thought were missing; dark mode, better syntax highlighting, and the ability to design with components.&lt;/p&gt;

&lt;p&gt;Gatsby starters are boilerplate Gatsby sites &lt;a href="https://www.gatsbyjs.org/docs/starters/"&gt;maintained by the community&lt;/a&gt;. One of the reasons I love them is that they use &lt;strong&gt;Semantic HTML&lt;/strong&gt;. This is great because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It helps with search engine optimization — web crawlers are able to understand which parts of your pages are important.&lt;/li&gt;
&lt;li&gt;It helps with accessibility — for people who use non-traditional browsers and screenreaders.&lt;/li&gt;
&lt;li&gt;It helps with maintenance — I was able to pick up a starter and understand what the different parts of the template referred to due to the semantic tags.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Take this example from &lt;code&gt;gatsby-starter-blog&lt;/code&gt; — the most popular starter and the base for my &lt;a href="https://healeycodes.com/"&gt;current blog&lt;/a&gt; (in-line styling removed).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;article&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="na"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;hr&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Bio&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;article&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I've seen quite a few beginner web development resources that skip on semantic HTML and encourage what I'll call 'div-spamming'. The HTML5 spec weighs in on this &lt;a href="https://html.spec.whatwg.org/multipage/grouping-content.html#the-div-element"&gt;issue&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Authors are strongly encouraged to view the div element as an element of last resort, for when no other element is suitable. Use of more appropriate elements instead of the div element leads to better accessibility for readers and easier maintainability for authors.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Coming from Jekyll
&lt;/h3&gt;

&lt;p&gt;I started blogging with Jekyll a year ago because I hosted my blog on GitHub pages and it was the static site generator with the least friction. It was a great choice at the time as it enabled me to get up and running straight away.&lt;/p&gt;

&lt;p&gt;I've seen many people warning others (engineers in particular) to avoid rolling their own blogging solutions. The advice is that you should start writing and publishing first. This is because building a blog can function as procrastination and who knows if you actually enjoy blogging (the activity) or the idea of having blogged (the achievement).&lt;/p&gt;

&lt;p&gt;With Jekyll, I used basic markdown and transferring written content to Gatsby wasn't too hard. Images had to be moved from one disorganized folder into separate folders. URLs were a bit of a pain and took 1.5hrs of manual work. I wanted all of my old posts to keep their location on the web so I added a front matter tag called &lt;code&gt;path&lt;/code&gt; to override the default naming scheme. My old URLs were too long and included categories (which I'm still to implement) so the path scheme from now on will be the title only.&lt;/p&gt;

&lt;p&gt;I extended &lt;code&gt;onCreateNode&lt;/code&gt; in &lt;code&gt;gatsby-node.js&lt;/code&gt;. I'm not sure if this is the best practice way to implement this feature but it works great so far.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onCreateNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createNodeField&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;`MarkdownRemark`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Check to use legacy path&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontmatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;createFilePath&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;createNodeField&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`slug`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Syntax highlighting
&lt;/h3&gt;

&lt;p&gt;Code excerpts show up in a lot of my posts and I like them to be easy to parse.&lt;/p&gt;

&lt;p&gt;I installed &lt;a href="https://www.gatsbyjs.org/packages/gatsby-remark-prismjs/"&gt;gatsby-remark-prismjs&lt;/a&gt; for syntax highlighting and was up and running in about an hour with another hour spent tinkering styles to match my light/dark mode toggle. I use &lt;a href="https://github.com/taniarascia/new-moon"&gt;New Moon Theme&lt;/a&gt; by Tania Rascia for my code excerpts. I couldn't find a version of the theme for PrismJS so I extracted the styling from Tania's (MIT-licensed) &lt;a href="https://www.taniarascia.com/"&gt;blog&lt;/a&gt;. My site's general color theme is custom.&lt;/p&gt;

&lt;p&gt;One of the reasons I'm mentioning plugins is that I found it hard to integrate them with Jekyll and I feel like it wasn't just my inexperience with Ruby that was holding me back. Perhaps it's due to the hype surrounding Gatsby which means there are up-to-date resources. I've contributed one (small) open-source fix to the Jekyll project and I would still recommend it for anyone looking for a sensible system for HTML/CSS that has wide community support e.g., GitHub pages, Netlify, etc. If you want to avoid JavaScript, Jekyll is the way to go.&lt;/p&gt;

&lt;p&gt;For my light/dark mode I use &lt;code&gt;gatsby-plugin-dark-mode&lt;/code&gt; which works well out of the box and has good (but not great) documentation. For theme-toggling, I researched and found that a common pattern was to declare CSS variables in body scope and then to override these variables in &lt;em&gt;class&lt;/em&gt; scope. This way, the &lt;code&gt;dark&lt;/code&gt; class can be added to the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tag which means &lt;code&gt;dark&lt;/code&gt; CSS variables take precedence due to CSS Specificity. Classes are then toggled on and off the &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, storing the preference in local browser storage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#eaeaeb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--textNormal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#414158&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="nc"&gt;.dark&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#21212c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--textNormal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#eaeaeb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Designing with components
&lt;/h3&gt;

&lt;p&gt;The first React component I wrote for my blog was for wrapping the &lt;code&gt;&amp;lt;ThemeToggler /&amp;gt;&lt;/code&gt; from &lt;code&gt;gatsby-plugin-dark-mode&lt;/code&gt; into a component. It switches between a sun and a moon to let the user know which theme can be switched to. The base for this is the example code from the &lt;a href="https://www.gatsbyjs.org/packages/gatsby-plugin-dark-mode/"&gt;docs&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ThemeToggler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toggleTheme&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`pointer`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
        &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`none`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt;
        &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;toggleTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;checked&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`☀️`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`🌑`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ThemeToggler&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I've never used React as part of a blogging solution. I like the hierarchical UI approach that's encouraged. Including CSS-in-JS makes sense for the scale of my website. It's easier for me to reason about and quickly tinker with.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I deploy
&lt;/h3&gt;

&lt;p&gt;My website source exists in a GitHub repository. I write in markdown in VS Code, commit, and push. Netlify is connected to the repository and builds and deploys every commit to master. The build process takes 2m50s (30s locally).&lt;/p&gt;

&lt;p&gt;I previously used Netlify for Jekyll and setting up either static site generator involved about 10 clicks and entering one or two build commands. The walkthrough covers everything.&lt;/p&gt;

&lt;p&gt;Overall, the Gatsby experience has been very enjoyable. Everywhere I look in my online bubble (Twitter, DEV, lobste.rs) people are talking about Gatsby. It's nice to be part of the crowd.&lt;/p&gt;

&lt;p&gt;I'm also happy that my site (despite being React-based) works fine without JavaScript enabled (barring the theme toggle, which I might hide with &lt;code&gt;&amp;lt;noscript&amp;gt;&lt;/code&gt; styling).&lt;/p&gt;




&lt;p&gt;Join 200+ people signed up to my &lt;a href="https://buttondown.email/healeycodes"&gt;newsletter&lt;/a&gt; on programming and personal growth!&lt;/p&gt;

&lt;p&gt;I tweet about code &lt;a href="https://twitter.com/healeycodes"&gt;@healeycodes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>css</category>
    </item>
    <item>
      <title>Hardest JavaScript Puzzle I've Ever Solved</title>
      <dc:creator>Andrew Healey</dc:creator>
      <pubDate>Sun, 17 Nov 2019 20:20:01 +0000</pubDate>
      <link>https://dev.to/healeycodes/hardest-javascript-puzzle-i-ve-ever-solved-bci</link>
      <guid>https://dev.to/healeycodes/hardest-javascript-puzzle-i-ve-ever-solved-bci</guid>
      <description>&lt;p&gt;I love code puzzles. Always have. My deskmate told me about a puzzle that no one in the office had been able to solve when they shared it around. I was intrigued because of the challenge but also because it was a JavaScript puzzle.&lt;/p&gt;

&lt;p&gt;I'd understand if it were a Project Euler problem (they can be mathy) or perhaps if no one had found the optimal solution to a LeetCode 'Hard' (I've seen some problems that once upon a time were research papers).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The puzzle took me two weeks to solve&lt;/strong&gt;. I became stuck almost instantly and then it hung around in the back of my mind until the solution came to me. First, let's take a look at the easier version of the problem which helped me unlock the harder version.&lt;/p&gt;

&lt;p&gt;Don't scroll too fast unless you want spoilers.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.codewars.com/kata/5935558a32fb828aad001213"&gt;Codewars: Multi Line Task++: Hello World&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Write a function &lt;code&gt;f&lt;/code&gt; that returns &lt;code&gt;Hello, world!&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every line must have at most 2 characters, and the total number of lines must be less than 40.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without the line restriction. The solution is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// or&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I started splitting up the code and shortened the variable declaration. We can throw away the &lt;code&gt;const&lt;/code&gt; and allow the function to exist in the global scope. We can also use template strings to break up the string into multiple lines.&lt;/p&gt;

&lt;p&gt;Errors incoming.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;f&lt;/span&gt;
&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// Uncaught SyntaxError: Unexpected token '=&amp;gt;'&lt;/span&gt;
&lt;span class="s2"&gt;`H
el
l,
 w
or
ld
!`&lt;/span&gt; &lt;span class="c1"&gt;// This string has newline characters in it!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;My next idea was to define the function inside an object and then retrieve the function out of the object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;
&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Too long!&lt;/span&gt;
&lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`
g
`&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;// 'Beautified'&lt;/span&gt;
&lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`
g
`&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;No errors, I was declaring a function but now I couldn't figure out how to return from the &lt;em&gt;inner&lt;/em&gt; function without using the &lt;code&gt;return&lt;/code&gt; keyword. It felt like I was close but I wasn't. I was still stuck on defining the string without newline characters as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding inspiration: &lt;a href="http://www.jsfuck.com/"&gt;JSFuck&lt;/a&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;JSFuck is an esoteric and educational programming style based on the atomic parts of JavaScript. It uses only six different characters to write and execute code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reading through this project's &lt;a href="https://github.com/aemkei/jsfuck/blob/master/jsfuck.js"&gt;source code&lt;/a&gt; really opened my mind to some parts of JavaScript that never really come up unless you're doing something like writing a library or code golfing.&lt;/p&gt;

&lt;p&gt;Once I figured out how to remove the newline characters from the &lt;code&gt;Hello, world!&lt;/code&gt; message (escaping with a backslash &lt;code&gt;\&lt;/code&gt;) everything else fell into place. I could now use square brackets &lt;code&gt;[]&lt;/code&gt; on objects to run pretty much anything. However, one of the requirements was to keep the total line count under 40 (one of the reasons why using JSFuck encoding was out of the question).&lt;/p&gt;

&lt;p&gt;My idea was to create a new function by calling &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind"&gt;Function.prototype.bind&lt;/a&gt; on a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String"&gt;String&lt;/a&gt; method. I used &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim"&gt;String.prototype.trim&lt;/a&gt; because it had the shortest name (and conveniently got rid of any trailing newlines too).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="dl"&gt;''&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;
t&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;
r&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;
i&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;
m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;][&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt; // Get a new function where `this` is "Hello, world!"
b&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;
i&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;
n&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;
d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="s2"&gt;` // Tagged template, see below
H\
e\
l\
l\
o\
,\
 \
w\
o\
r\
l\
d\
!`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I also used tagged templates to pass &lt;code&gt;Hello, world!&lt;/code&gt; as an argument to bind.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tags allow you to parse template literals with a function. The first argument of a tag function contains an array of string values.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's take it up a level to the harder version that started this journey!&lt;/p&gt;

&lt;h3&gt;
  
  
  Codewars: &lt;a href="https://www.codewars.com/kata/59a421985eb5d4bb41000031"&gt;Multi Line Task∞: Hello World&lt;/a&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Write a function &lt;code&gt;f&lt;/code&gt; that returns &lt;code&gt;Hello, world!&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every line must have &lt;strong&gt;at most 1 character&lt;/strong&gt;, and the total number of lines must be less than 145.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without having first solved the two-characters-per-line version I don't think I would have come close to answering this version.&lt;/p&gt;

&lt;p&gt;The solution I went for is the same, we use &lt;code&gt;bind&lt;/code&gt; on &lt;code&gt;trim&lt;/code&gt; and pass the message as an argument (without template tags this time). To access the String object we use &lt;code&gt;[]+[]&lt;/code&gt; which evaluates to &lt;code&gt;""&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since we can no longer escape the newline character from within a template string we have to use a workaround. The property names (&lt;code&gt;trim&lt;/code&gt;, &lt;code&gt;bind&lt;/code&gt;) and the message (&lt;code&gt;Hello, world!&lt;/code&gt;) have to be built with concatenated variables.&lt;/p&gt;

&lt;p&gt;We use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment"&gt;destructuring assignment&lt;/a&gt; on a template string and use empty slots in the first array to 'skip' assigning the newline character to anything. Like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[,&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xHxix!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="c1"&gt;// avoid the "x"s&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="c1"&gt;// Evaluates to: "Hi!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I didn't optimize the solution any further once it passed the requirements. It's been left verbose to better explain what's going on (for example, we only need one "l" in the template string).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="c1"&gt;// With destructuring assignment, start declaring variables&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;t&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;r&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;i&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;m&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;b&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;i&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;n&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;d&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;H&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;e&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;l&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;l&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;o&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="c1"&gt;// Comma&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;s&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;w&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;o&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;r&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;l&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;d&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="c1"&gt;// Exclamation mark&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="s2"&gt;`
t
r
i
m
b
i
n
d
H
e
l
l
o
,

w
o
r
l
d
!
`&lt;/span&gt;
&lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="c1"&gt;// Start declaring our function&lt;/span&gt;
&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c1"&gt;// This evaluates to "" or, the String object&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="c1"&gt;// `trim`&lt;/span&gt;
&lt;span class="nx"&gt;t&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;r&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;i&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;m&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="c1"&gt;// `bind`&lt;/span&gt;
&lt;span class="nx"&gt;b&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;i&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;n&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;d&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c1"&gt;// Can use parentheses or template tag syntax to call `bind`&lt;/span&gt;
&lt;span class="nx"&gt;H&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;e&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;l&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;l&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;o&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;c&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;s&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;w&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;o&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;r&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;l&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;d&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="nx"&gt;x&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;p&gt;I'm definitely taking a break from language-specific coding puzzles — give me logic over syntax! However, I'm glad I scratched this itch.&lt;/p&gt;




&lt;p&gt;Join 150+ people signed up to my &lt;a href="https://buttondown.email/healeycodes"&gt;newsletter&lt;/a&gt; on programming and personal growth!&lt;/p&gt;

&lt;p&gt;And tweet about tech &lt;a href="https://twitter.com/healeycodes"&gt;@healeycodes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>challenge</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
