<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://gabriels.cloud/feed.xml" rel="self" type="application/atom+xml" /><link href="https://gabriels.cloud/" rel="alternate" type="text/html" /><updated>2026-08-31T02:17:03+02:00</updated><id>https://gabriels.cloud/feed.xml</id><title type="html">Gabriel’s Website</title><subtitle>My personal blog and portfolio in one place.</subtitle><author><name>Gabriel</name></author><entry><title type="html">Teaching the date command a new format</title><link href="https://gabriels.cloud/2026/08/teaching-the-date-command-a-new-format/" rel="alternate" type="text/html" title="Teaching the date command a new format" /><published>2026-08-31T00:19:00+02:00</published><updated>2026-08-31T00:19:00+02:00</updated><id>https://gabriels.cloud/2026/08/teaching-the-date-command-a-new-format</id><content type="html" xml:base="https://gabriels.cloud/2026/08/teaching-the-date-command-a-new-format/"><![CDATA[<p>The <code class="language-plaintext highlighter-rouge">date</code> command from GNU coreutils finally supports the date format <code class="language-plaintext highlighter-rouge">dd.mm.yyyy</code> and its short form <code class="language-plaintext highlighter-rouge">dd.mm.</code> with
the <a href="https://lists.gnu.org/archive/html/info-gnu/2026-04/msg00006.html">release of coreutils-9.11</a> on 20 April (or as
you can now write: <code class="language-plaintext highlighter-rouge">date -d 20.4.</code>). From a quick glance
at <a href="https://en.wikipedia.org/wiki/List_of_date_formats_by_country#Table_coding">Wikipedia</a>, it’s used in over 30
countries, especially in and around Europe.</p>

<p>Setting up a build environment with both repositories (coreutils and gnulib) was quite a challenge, due to git
submodules and autotools. The necessary patches, on the other hand, looked quite straightforward: add the new formats in
<code class="language-plaintext highlighter-rouge">lib/parse-datetime.y</code> and have <a href="https://www.gnu.org/software/bison/manual/html_node/Grammar-File.html">bison</a> generate
the new parser.</p>

<p>But it didn’t work: I still got bad format errors. However, if I used anything other than a dot as a separator (such as
a comma or a pipe), it did work. This led me
to <a href="https://github.com/coreutils/gnulib/blob/de054f5dc8906dbac0054e113d7d4fd0430a3398/lib/parse-datetime.y#L1492">the logic</a>
that <em>parses</em> decimal/nanoseconds in the <em>lexer</em>, which is not intuitive. I didn’t want to touch too much unrelated
code, so I added a flag to not mangle the new format. I might revisit this someday if I come up with a cleaner design.</p>

<p>Then I added some tests and the docs, which was mostly straightforward again. The tests are rather sparse, spread over
the two repositories and often use Perl or Bash scripts, which I had to understand first. Despite my attempts, I also
didn’t get the GNU formatting completely right. Thankfully, the maintainer handled that and the test failure on Windows
(apparently “Year 0003 is not supported there”). Thanks, Pádraig Brady.</p>

<h2 id="when-can-i-use-it">When can I use it?</h2>

<p>On rolling-release and faster-updating distros like Fedora, right now. On Debian, probably with the next release on a
yet to be announced date.</p>

<p>On Ubuntu, you’ll need to wait until they release a new version based on the next Debian and then install the GNU
coreutils yourself. Ubuntu
now <a href="https://discourse.ubuntu.com/t/carefully-but-purposefully-oxidising-ubuntu/56995">uses uutils</a> by default, the
<em>Rewrite in Rust</em>︎™ of coreutils.</p>

<p>All in all, I got to explore a foundational piece of GNU/Linux distributions, and learned how to send git patches via
mail. Let’s see if I get to improve the Rust version, too.</p>]]></content><author><name>Gabriel</name></author><category term="blog" /><category term="coreutils" /><summary type="html"><![CDATA[The date command from GNU coreutils finally supports the date format dd.mm.yyyy and its short form dd.mm. with the release of coreutils-9.11 on 20 April (or as you can now write: date -d 20.4.). From a quick glance at Wikipedia, it’s used in over 30 countries, especially in and around Europe.]]></summary></entry><entry><title type="html">How to stream data into Grafana Live the simple way</title><link href="https://gabriels.cloud/2025/06/how-to-stream-data-into-grafana-live-the-simple-way/" rel="alternate" type="text/html" title="How to stream data into Grafana Live the simple way" /><published>2025-06-15T12:41:00+02:00</published><updated>2025-06-15T12:41:00+02:00</updated><id>https://gabriels.cloud/2025/06/how-to-stream-data-into-grafana-live-the-simple-way</id><content type="html" xml:base="https://gabriels.cloud/2025/06/how-to-stream-data-into-grafana-live-the-simple-way/"><![CDATA[<p>Over at <a href="https://warr.de/en/projects/move/move-up/">MOVE-UP</a> we’re already using Grafana to display sensor data like altitude and speed.
However, storing into and fetching it again from a database takes some seconds while we’d rather have that information live.</p>

<p>That’s where Grafana Live comes in, which allows you to stream data in (soft) real-time. For example, let’s say we have the data in a Python
script:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">random</span>
<span class="kn">import</span> <span class="n">time</span>

<span class="n">alt</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="nf">uniform</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1000</span><span class="p">)</span>
<span class="n">speed</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="nf">uniform</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">)</span>

<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">500</span><span class="p">):</span>
    <span class="n">alt</span> <span class="o">+=</span> <span class="n">random</span><span class="p">.</span><span class="nf">uniform</span><span class="p">(</span><span class="o">-</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
    <span class="n">speed</span> <span class="o">+=</span> <span class="n">random</span><span class="p">.</span><span class="nf">uniform</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">time</span><span class="p">.</span><span class="nf">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</code></pre></div></div>

<p>How do we get it into Grafana?</p>

<picture><source srcset="/generated/grafana-live-400-3e55574e9.webp 400w, /generated/grafana-live-942-3e55574e9.webp 942w" type="image/webp" /><source srcset="/generated/grafana-live-400-f966d4dc2.png 400w, /generated/grafana-live-942-f966d4dc2.png 942w" type="image/png" /><img loading="lazy" src="/generated/grafana-live-800-f966d4dc2.png" alt="meme of a guy sweating to decide between two buttons labeled curl -X POST and npm template package + tutorial" /></picture>

<p>The <a href="https://grafana.com/docs/grafana/latest/setup-grafana/set-up-grafana-live/">official documentation</a> suggests creating a full-blown
TypeScript + Go plugin or setup Telegraf.
None of that is necessary, you can just use the new API endpoint <code class="language-plaintext highlighter-rouge">/api/live/push/:streamId</code> directly:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">os</span>
<span class="kn">import</span> <span class="n">random</span>
<span class="kn">import</span> <span class="n">requests</span>
<span class="kn">import</span> <span class="n">time</span>

<span class="n">url</span> <span class="o">=</span> <span class="sh">"</span><span class="s">http://localhost:3000/api/live/push/test_stream</span><span class="sh">"</span>
<span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="sh">"</span><span class="s">Authorization</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">Bearer </span><span class="sh">"</span> <span class="o">+</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">[</span><span class="sh">"</span><span class="s">AUTH</span><span class="sh">"</span><span class="p">]}</span>

<span class="n">alt</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="nf">uniform</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1000</span><span class="p">)</span>
<span class="n">speed</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="nf">uniform</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">100</span><span class="p">)</span>

<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">500</span><span class="p">):</span>
    <span class="n">alt</span> <span class="o">+=</span> <span class="n">random</span><span class="p">.</span><span class="nf">uniform</span><span class="p">(</span><span class="o">-</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
    <span class="n">speed</span> <span class="o">+=</span> <span class="n">random</span><span class="p">.</span><span class="nf">uniform</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="sa">f</span><span class="sh">"</span><span class="s">balloon speed=</span><span class="si">{</span><span class="n">speed</span><span class="si">}</span><span class="s">,alt=</span><span class="si">{</span><span class="n">alt</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">response</span><span class="p">.</span><span class="n">status_code</span> <span class="o">!=</span> <span class="mi">200</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Error: </span><span class="si">{</span><span class="n">response</span><span class="p">.</span><span class="n">status_code</span><span class="si">}</span><span class="s"> - </span><span class="si">{</span><span class="n">response</span><span class="p">.</span><span class="n">text</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

    <span class="n">time</span><span class="p">.</span><span class="nf">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</code></pre></div></div>

<p>Authentication is done with
an <a href="https://grafana.com/docs/grafana/latest/administration/service-accounts/#create-a-service-account-in-grafana">API token</a>.</p>

<p>Thanks to <a href="https://community.grafana.com/t/how-to-connect-streaming-data-source-to-websocket/54927/2">Grafana89e9</a>
and <a href="https://community.grafana.com/t/how-to-connect-streaming-data-source-to-websocket/54927/4">Smaragda Benetou</a> for pointing this out with
code examples.</p>]]></content><author><name>Gabriel</name></author><category term="how-to" /><category term="grafana" /><category term="ops" /><category term="python" /><summary type="html"><![CDATA[Over at MOVE-UP we’re already using Grafana to display sensor data like altitude and speed. However, storing into and fetching it again from a database takes some seconds while we’d rather have that information live.]]></summary></entry><entry><title type="html">Performance matters</title><link href="https://gabriels.cloud/2025/04/performance-matters/" rel="alternate" type="text/html" title="Performance matters" /><published>2025-04-18T16:16:00+02:00</published><updated>2025-04-18T16:16:00+02:00</updated><id>https://gabriels.cloud/2025/04/performance-matters</id><content type="html" xml:base="https://gabriels.cloud/2025/04/performance-matters/"><![CDATA[<blockquote>
  <p>A lot of people seem to think that performance is about doing the same thing, just doing it faster. And that’s not true. That’s not what
performance is all about. If you can do something really fast, really well, people start using it differently.
<cite><a href="https://www.youtube.com/watch?v=idLyobOhtO4&amp;t=3030s">Linus Torvalds</a></cite></p>
</blockquote>]]></content><author><name>Gabriel</name></author><category term="blog" /><category term="performance" /><summary type="html"><![CDATA[A lot of people seem to think that performance is about doing the same thing, just doing it faster. And that’s not true. That’s not what performance is all about. If you can do something really fast, really well, people start using it differently. Linus Torvalds]]></summary></entry><entry><title type="html">Conditional Dependencies in Rust</title><link href="https://gabriels.cloud/2025/03/conditional-dependencies-in-rust/" rel="alternate" type="text/html" title="Conditional Dependencies in Rust" /><published>2025-03-08T20:32:00+01:00</published><updated>2025-03-08T20:32:00+01:00</updated><id>https://gabriels.cloud/2025/03/conditional-dependencies-in-rust</id><content type="html" xml:base="https://gabriels.cloud/2025/03/conditional-dependencies-in-rust/"><![CDATA[<p>You can <a href="https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#platform-specific-dependencies">conditionally include dependencies</a>
in <code class="language-plaintext highlighter-rouge">Cargo.toml</code> based on the compile target.
If a crate requires SSE2 for example, put it in this section</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">[</span><span class="n">target</span><span class="k">.</span><span class="s">'cfg(target_feature = "sse2")'</span><span class="k">.</span><span class="n">dependencies</span><span class="k">]</span>
<span class="n">sse2_only_crate</span> <span class="o">=</span><span class="w"> </span><span class="s">"0.0.1"</span>
</code></pre></div></div>]]></content><author><name>Gabriel</name></author><category term="how-to" /><category term="rust" /><summary type="html"><![CDATA[You can conditionally include dependencies in Cargo.toml based on the compile target. If a crate requires SSE2 for example, put it in this section [target.'cfg(target_feature = "sse2")'.dependencies] sse2_only_crate = "0.0.1"]]></summary></entry><entry><title type="html">Embedding Python in Elixir, it’s great</title><link href="https://gabriels.cloud/2025/03/embedding-python-in-elixir-it-s-great/" rel="alternate" type="text/html" title="Embedding Python in Elixir, it’s great" /><published>2025-03-03T10:44:00+01:00</published><updated>2025-03-03T10:44:00+01:00</updated><id>https://gabriels.cloud/2025/03/embedding-python-in-elixir-it-s-great</id><content type="html" xml:base="https://gabriels.cloud/2025/03/embedding-python-in-elixir-it-s-great/"><![CDATA[<p>Elixir is getting somewhat official support for
Python: <a href="https://dashbit.co/blog/running-python-in-elixir-its-fine">Embedding Python in Elixir, it’s Fine</a>.
While there are already <a href="http://erlport.org/docs/python.html">other</a> <a href="https://github.com/Pyrlang/Pyrlang">ways</a> to run Python, the
integration
with <a href="https://livebook.dev/">Livebook</a> and <a href="https://github.com/elixir-nx">Nx</a> is new.<br />
Coincidentally, I’ve been working on a project that uses Python and LLMs.
Writing <a href="https://mensa.gabriels.cloud/">Mensaplan</a> in Elixir plus Phoenix was such a nice experience I’m considering using it for the
user-facing part of that project too, so this is not just Fine, but great.</p>]]></content><author><name>Gabriel</name></author><category term="blog" /><category term="elixir" /><category term="python" /><summary type="html"><![CDATA[Elixir is getting somewhat official support for Python: Embedding Python in Elixir, it’s Fine. While there are already other ways to run Python, the integration with Livebook and Nx is new. Coincidentally, I’ve been working on a project that uses Python and LLMs. Writing Mensaplan in Elixir plus Phoenix was such a nice experience I’m considering using it for the user-facing part of that project too, so this is not just Fine, but great.]]></summary></entry><entry><title type="html">Inventing on Principle</title><link href="https://gabriels.cloud/2025/02/inventing-on-principle/" rel="alternate" type="text/html" title="Inventing on Principle" /><published>2025-02-18T15:43:00+01:00</published><updated>2025-04-02T17:15:00+02:00</updated><id>https://gabriels.cloud/2025/02/inventing-on-principle</id><content type="html" xml:base="https://gabriels.cloud/2025/02/inventing-on-principle/"><![CDATA[<p>I’ve only now discovered Bret Victor’s incredible talk <a href="https://vimeo.com/906418692">Inventing on Principle</a>.
While you should definitely watch the entire talk, I was particularly fascinated by the tech demos.</p>

<h2 id="hot-reloading">Hot reloading</h2>

<p>The first thing Victor demonstrates is already cool: hot reloading. Any code change is immediately visible on the canvas.
13 years later, it’s pretty much everywhere, from frontend
frameworks (<a href="https://docs.flutter.dev/tools/hot-reload">Flutter</a>, <a href="https://reactnative.dev/blog/2016/03/24/introducing-hot-reloading">React</a>, <a href="https://vite.dev/guide/features.html#hot-module-replacement">Vite</a>, <a href="https://webpack.js.org/concepts/hot-module-replacement/">Webpack</a>)
to game
engines (<a href="https://docs.unity3d.com/Manual/configurable-enter-play-mode.html">Unity</a>, <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/using-live-coding-to-recompile-unreal-engine-applications-at-runtime">Unreal Engine</a>).</p>

<h2 id="what-does-the-code-do">What does the code do?</h2>

<p>Now, let’s step up the game. Take a look at this:</p>

<picture><source srcset="/generated/iop/magnifying-glass-400-2fc6f8eec.webp 400w, /generated/iop/magnifying-glass-1200-2fc6f8eec.webp 1200w" type="image/webp" /><source srcset="/generated/iop/magnifying-glass-400-31e7ef590.png 400w, /generated/iop/magnifying-glass-1200-31e7ef590.png 1200w" type="image/png" /><img loading="lazy" src="/generated/iop/magnifying-glass-800-31e7ef590.png" alt="open book: on the right is JavaScript code, on the left a cherry tree in front of red mountains. A magnifying glass hovers over the code for drawing mountains" /></picture>

<p>The connection between code and result is immediately visible.
It also works the other way round; you can select any pixel to highlight
the code responsible for painting it. Element picking is present for many GUI toolkits; going from code to result, however, is exceedingly
rare.</p>

<h2 id="visualizing-time">Visualizing time</h2>

<p>Visualizing a program’s behavior over time is immensely powerful, not just for games. <a href="https://rr-project.org/">The rr debugger</a>, for
example, applies this concept, and the <a href="https://github.com/rr-debugger/rr/wiki/Testimonials">list of testimonials</a> proves this is wanted.
Of course, Victor takes this to the next level, also allowing changes and visualizing the new timeline.</p>

<picture><source srcset="/generated/iop/time-400-21d8739fb.webp 400w, /generated/iop/time-984-21d8739fb.webp 984w" type="image/webp" /><source srcset="/generated/iop/time-400-1aee0619b.png 400w, /generated/iop/time-984-1aee0619b.png 984w" type="image/png" /><img loading="lazy" src="/generated/iop/time-800-1aee0619b.png" alt="main character in a platformer game jumps, translucent copies show his movement over time" /></picture>

<h2 id="debug-programming">Debug programming</h2>

<p>How do we debug programs?<br />
With interactive tools showing us the state of the program for specific arguments, so we can confirm our assumptions.</p>

<p>Why is it a separate step from programming? Why do we only use this once bugs have already caused issues?</p>

<p>Well, Victor does it better. By including the debugger in the programming environment, we can immediately see the state of the program for
any input we want.</p>

<picture><source srcset="/generated/iop/binary-search-400-86b59ed17.webp 400w, /generated/iop/binary-search-1200-86b59ed17.webp 1200w" type="image/webp" /><source srcset="/generated/iop/binary-search-400-988ba2f8a.png 400w, /generated/iop/binary-search-1200-988ba2f8a.png 1200w" type="image/png" /><img loading="lazy" src="/generated/iop/binary-search-800-988ba2f8a.png" alt="binary search in JavaScript next to its variables and their values for an example execution" /></picture>

<p>2025-02-28: There has been <a href="https://www.kickstarter.com/projects/ibdknox/light-table">an attempt</a> to create such an IDE.</p>

<p>2025-04-02: Obviously <a href="https://jupyter.org/">Jupyter Notebooks</a>, and their <a href="https://marimo.io/blog/python-not-json">extensions</a> are a step in this direction.</p>

<h2 id="conclusion">Conclusion</h2>

<p><em>Inventing on Principle</em> is a really great talk with incredibly good ideas. If I get the time, I’ll implement some of these.</p>]]></content><author><name>Gabriel</name></author><category term="blog" /><category term="design" /><category term="tools" /><summary type="html"><![CDATA[I’ve only now discovered Bret Victor’s incredible talk Inventing on Principle. While you should definitely watch the entire talk, I was particularly fascinated by the tech demos.]]></summary></entry><entry><title type="html">How to send a Bluetooth file from a Mac</title><link href="https://gabriels.cloud/2025/01/how-to-send-a-bluetooth-file-from-a-mac/" rel="alternate" type="text/html" title="How to send a Bluetooth file from a Mac" /><published>2025-01-29T17:52:00+01:00</published><updated>2025-01-29T17:52:00+01:00</updated><id>https://gabriels.cloud/2025/01/how-to-send-a-bluetooth-file-from-a-mac</id><content type="html" xml:base="https://gabriels.cloud/2025/01/how-to-send-a-bluetooth-file-from-a-mac/"><![CDATA[<p>While AirDrop is great for quickly sharing files between devices, it’s a non-starter for anything not Apple.
Luckily, other standards like Bluetooth exist.
Unfortunately, the third step is missing in
the <a href="https://support.apple.com/guide/mac-help/share-files-mac-bluetooth-devices-mchle7fa9e15/mac#apd2cb9d2d52f204">help page</a>
and the UI is not helpful.
So here’s the full explanation for sending a file from a Mac to a Bluetooth device.</p>

<ol>
  <li>Enable Bluetooth sharing in Settings &gt; General &gt; Sharing<br />
See <a href="https://support.apple.com/guide/mac-help/set-up-bluetooth-sharing-on-mac-mchlp1673/15.0/mac">Apple’s guide</a> for
a longer explanation</li>
  <li>Now, this is the same as in Apple’s guide
    <blockquote>
      <p>Open the Bluetooth File Exchange (located in the Utilities folder in the Applications folder)</p>
    </blockquote>
  </li>
  <li>The guide’s step 2 is impossible here, File &gt; Send File is greyed out. You’ll get a popup that lets you browse other
devices. We don’t want that, so click Cancel, and then, while the same app is still selected, you can finally</li>
  <li>use File &gt; Send File.</li>
  <li>Select the file and device you want and click Send.</li>
</ol>

<picture><source srcset="/generated/cats/basket-400-d9c1e4d1d.webp 400w, /generated/cats/basket-1200-d9c1e4d1d.webp 1200w" type="image/webp" /><source srcset="/generated/cats/basket-400-da7928b6f.jpg 400w, /generated/cats/basket-1200-da7928b6f.jpg 1200w" type="image/jpeg" /><img loading="lazy" src="/generated/cats/basket-800-da7928b6f.jpg" alt="cat sitting in a basket looking at the camera" /></picture>]]></content><author><name>Gabriel</name></author><category term="how-to" /><category term="apple" /><category term="bluetooth" /><category term="cat" /><summary type="html"><![CDATA[While AirDrop is great for quickly sharing files between devices, it’s a non-starter for anything not Apple. Luckily, other standards like Bluetooth exist. Unfortunately, the third step is missing in the help page and the UI is not helpful. So here’s the full explanation for sending a file from a Mac to a Bluetooth device.]]></summary></entry><entry><title type="html">RGButtons: Creating Animated Rainbow Borders for Buttons</title><link href="https://gabriels.cloud/2024/08/rgbuttons-creating-animated-rainbow-borders-for-buttons/" rel="alternate" type="text/html" title="RGButtons: Creating Animated Rainbow Borders for Buttons" /><published>2024-08-26T10:16:00+02:00</published><updated>2024-08-26T10:16:00+02:00</updated><id>https://gabriels.cloud/2024/08/rgbuttons-creating-animated-rainbow-borders-for-buttons</id><content type="html" xml:base="https://gabriels.cloud/2024/08/rgbuttons-creating-animated-rainbow-borders-for-buttons/"><![CDATA[<p>Recently I did some frontend work and wanted to spice up my buttons.
I like rainbow colors and nice animations, so why not give buttons a moving rainbow border on hover?
CSS is powerful but often has multiple ways that appear to accomplish the same thing, at least on the surface.
The devil is in the details, so I tried different approaches to achieve this effect.</p>

<p>First, <code class="language-plaintext highlighter-rouge">border</code> supports gradients, so maybe we can use this property?</p>

<iframe loading="lazy" width="100%" height="300" src="https://codepen.io/bramus/embed/rNWByYz" frameborder="0"></iframe>

<p>Taken from <a href="https://www.bram.us/2021/01/29/animating-a-css-gradient-border/">this excellent blog post</a>, it’s almost what
I want. Unfortunately, though, it doesn’t support rounded edges.
Also, it uses a custom property, and despite the post being three years
old, <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSS_Properties_and_Values_API">custom properties</a> are still not a
thing in all browsers. There’s <a href="https://codepen.io/bramus/pen/XWMwPgO">a workaround</a> included, essentially generating
the expected animation for the browser, but it’s a lot of boilerplate, and I don’t like bloating my file just for this.</p>

<p>Another option is using a pseudo-element behind the button and setting its <code class="language-plaintext highlighter-rouge">background</code>. I played around with this, and
it seemed like the best option.
Unfortunately, I only found an example with a linear gradient while I wanted a conic one. Changing the gradient type is
easy, but then the animation was broken. Moving the <code class="language-plaintext highlighter-rouge">background-position</code> works only for the scaled up linear gradient.
If I apply a rotate animation directly, we get this:</p>

<iframe loading="lazy" width="100%" height="200" src="//jsfiddle.net/6wmtxedg/embedded/result,css,html/dark/" frameborder="0"></iframe>

<p>We need to clip away anything not part of the border.
I searched for clipping but only ended up
with <a href="https://css-tricks.com/animating-border/#aa-method-3-cut-it-with-clip-path"><code class="language-plaintext highlighter-rouge">clip-path</code></a>, svgs and the likes,
which could maybe work, but would be a hassle. <code class="language-plaintext highlighter-rouge">masks</code> also didn’t look too promising.</p>

<p>Finally, <code class="language-plaintext highlighter-rouge">overflow</code> saved me. I hadn’t seen the <code class="language-plaintext highlighter-rouge">overflow: clip</code> property, and low and behold, it also comes
with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-margin">overflow-clip-margin</a>, giving us the space
for a beautiful border. This is perfect!
Now, I just had to center the pseudo-element, one of the most infamous tasks in CSS. Luckily <code class="language-plaintext highlighter-rouge">inset: 0</code> combined
with <code class="language-plaintext highlighter-rouge">margin: auto</code> gets us almost there, but for some reason, we also need to override <code class="language-plaintext highlighter-rouge">left</code> with the explicit
indentation to offset the oversize width.
This is the final result, and although it feels a bit cursed, it works™:</p>

<iframe loading="lazy" width="100%" height="200" src="https://jsfiddle.net/wqc4myzg/2/embedded/result,css,html/dark/" frameborder="0"></iframe>]]></content><author><name>Gabriel</name></author><category term="project" /><category term="web" /><category term="css" /><summary type="html"><![CDATA[Recently I did some frontend work and wanted to spice up my buttons. I like rainbow colors and nice animations, so why not give buttons a moving rainbow border on hover? CSS is powerful but often has multiple ways that appear to accomplish the same thing, at least on the surface. The devil is in the details, so I tried different approaches to achieve this effect.]]></summary></entry><entry><title type="html">How to export SQL databases with limited permissions</title><link href="https://gabriels.cloud/2024/08/how-to-export-sql-databases-with-limited-permissions/" rel="alternate" type="text/html" title="How to export SQL databases with limited permissions" /><published>2024-08-12T23:07:00+02:00</published><updated>2024-08-12T23:07:00+02:00</updated><id>https://gabriels.cloud/2024/08/how-to-export-sql-databases-with-limited-permissions</id><content type="html" xml:base="https://gabriels.cloud/2024/08/how-to-export-sql-databases-with-limited-permissions/"><![CDATA[<p>Sometimes you need to export a database, for backups or for transferring the data into another DB.
The usual way of exporting a Postgres database is to use <code class="language-plaintext highlighter-rouge">pg_dump</code>, but if you’re using a shared hosting provider, you
might not have the necessary permissions to do so.
Then any <code class="language-plaintext highlighter-rouge">pg_dump</code> commands will fail with an error like this:</p>

<p><code class="language-plaintext highlighter-rouge">pg_dump: error: query failed: ERROR:  permission denied for view pg_roles</code></p>

<p>I don’t know why <code class="language-plaintext highlighter-rouge">pg_dump</code> needs to access the <code class="language-plaintext highlighter-rouge">pg_roles</code> view, but it does and fails if it can’t.
Maybe we can find a way to skip this roles access? I don’t want any roles, I just want the data.<br />
There are multiple promising options in the help menu that I tried:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-O, --no-owner               skip restoration of object ownership in plain-text format
-a, --data-only              dump only the data, not the schema
-t, --table=PATTERN          dump the specified table(s) only
</code></pre></div></div>

<p>Unfortunately, none of them solved the problem, the error persisted.</p>

<p>So I searched for a different way to export the data and I found a way.
It’s not as elegant as <code class="language-plaintext highlighter-rouge">pg_dump</code>, but it works and that’s what counts.
We can use the <a href="https://www.postgresql.org/docs/current/sql-copy.html"><code class="language-plaintext highlighter-rouge">COPY</code> command</a> to move data between a table and
a file.</p>

<p>Here’s <a href="https://stackoverflow.com/a/63349501/12846952">StackOverflow’s way</a> to export a table from any SQL database into
a CSV file:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"</span><span class="se">\C</span><span class="s2">OPY (select * from table ) TO 'table.csv' (format CSV);"</span> <span class="o">&gt;</span> my_query.sql
psql &lt;database&gt; <span class="nt">-af</span> my_query.sql
</code></pre></div></div>
<p>We use the backslash to make it a client command, so psql will look in the current directory for the file.
If you use <code class="language-plaintext highlighter-rouge">COPY</code> without the backslash, the server will look for the file.</p>

<p>An even shorter answer without a helper file is found
in <a href="https://stackoverflow.com/questions/29190632/how-to-export-a-postgresql-query-output-to-a-csv-file#comment78536366_43606764">the comments</a>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>psql &lt;database&gt; <span class="nt">-c</span> <span class="s1">'select * from table limit 5'</span> <span class="nt">-tAF</span> , <span class="o">&gt;</span> table.csv
</code></pre></div></div>

<p>For importing the data back into a database, you can then use the <code class="language-plaintext highlighter-rouge">\COPY</code> command again:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>psql &lt;database&gt; <span class="nt">-a</span> <span class="nt">-c</span> <span class="s2">"</span><span class="se">\C</span><span class="s2">OPY table FROM 'table.csv' CSV"</span>
</code></pre></div></div>

<picture><source srcset="/generated/cats/orange/sleep-400-564110b32.webp 400w, /generated/cats/orange/sleep-1200-564110b32.webp 1200w" type="image/webp" /><source srcset="/generated/cats/orange/sleep-400-2d4312ce4.jpg 400w, /generated/cats/orange/sleep-1200-2d4312ce4.jpg 1200w" type="image/jpeg" /><img loading="lazy" src="/generated/cats/orange/sleep-800-2d4312ce4.jpg" alt="an orange cat happily sleeping on another cat's back" /></picture>]]></content><author><name>Gabriel</name></author><category term="how-to" /><category term="cat" /><category term="database" /><category term="sql" /><summary type="html"><![CDATA[Sometimes you need to export a database, for backups or for transferring the data into another DB. The usual way of exporting a Postgres database is to use pg_dump, but if you’re using a shared hosting provider, you might not have the necessary permissions to do so. Then any pg_dump commands will fail with an error like this:]]></summary></entry><entry><title type="html">I analysed 1 Million digits of Pi</title><link href="https://gabriels.cloud/2024/07/1-million-digits-of-pi-analysed/" rel="alternate" type="text/html" title="I analysed 1 Million digits of Pi" /><published>2024-07-07T15:56:00+02:00</published><updated>2024-07-07T15:56:00+02:00</updated><id>https://gabriels.cloud/2024/07/1-million-digits-of-pi-analysed</id><content type="html" xml:base="https://gabriels.cloud/2024/07/1-million-digits-of-pi-analysed/"><![CDATA[<p>I analysed the distribution of numbers in the first million digits of π.
You can download them for example
from <a href="https://www.pilookup.com/download.html">PILookup</a> (<a href="https://files.pilookup.com/pi/1-1000000.zip">direct download link</a>)
if you want to follow along.
Don’t forget to extract the zip file!</p>

<p>A simple Python script is enough to read the file and count the occurrences of each digit.
We can use a dictionary to store the counts, which is initialised with 0 for each digit.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="nf">open</span><span class="p">(</span><span class="sh">"</span><span class="s">1-1000000.txt</span><span class="sh">"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">pi</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="nf">read</span><span class="p">()</span>
    <span class="n">count</span> <span class="o">=</span> <span class="nf">dict</span><span class="p">((</span><span class="nf">str</span><span class="p">(</span><span class="n">i</span><span class="p">),</span> <span class="mi">0</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">10</span><span class="p">))</span>
    <span class="k">for</span> <span class="n">digit</span> <span class="ow">in</span> <span class="n">pi</span><span class="p">:</span>
        <span class="n">count</span><span class="p">[</span><span class="n">digit</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">count</span><span class="p">)</span>

</code></pre></div></div>

<p>Et voilà, the result:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{'0': 99959, '1': 99758, '2': 100026, '3': 100229, '4': 100230, '5': 100359, '6': 99548, '7': 99800, '8': 99985, '9': 100106}
</code></pre></div></div>

<p class="notice--info">This is only counting digits after the decimal point, to include the first 3 increase the count for 3 and take away the
1 at the millionth place.</p>

<p>If you want to see it as a nice graph, add the following code:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>

<span class="n">plt</span><span class="p">.</span><span class="nf">bar</span><span class="p">(</span><span class="n">count</span><span class="p">.</span><span class="nf">keys</span><span class="p">(),</span> <span class="n">count</span><span class="p">.</span><span class="nf">values</span><span class="p">())</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="/assets/images/million-digits-of-pi.svg" alt="million-digits-of-pi.svg" /></p>

<p>The distribution is pretty even, so you might consider using Pi as a (pseudo) random number generator.
It would be pretty good on its
own (<a href="https://www.purdue.edu/uns/html4ever/2005/050426.Fischbach.pi.html">study 1</a>, <a href="https://www.jstor.org/stable/2685604">study 2</a>, <a href="https://blogs.sas.com/content/iml/2015/03/12/digits-of-pi.html">10 million digits</a>),
but randomness is usually meant as unguessable numbers (at least without significant effort).
As soon as someone recognizes the sequence, the surprise is gone and for example getting “random” rewards in a game
becomes predictable and boring.
On the other hand, most people only know the first few digits of Pi, so if you start with a large enough offset, you
should be fine.</p>]]></content><author><name>Gabriel</name></author><category term="blog" /><category term="pi" /><category term="math" /><summary type="html"><![CDATA[I analysed the distribution of numbers in the first million digits of π. You can download them for example from PILookup (direct download link) if you want to follow along. Don’t forget to extract the zip file!]]></summary></entry></feed>