<?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-02-17T13:37:09+01: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">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-600-3e55574e9.webp 600w, /generated/grafana-live-800-3e55574e9.webp 800w, /generated/grafana-live-942-3e55574e9.webp 942w" type="image/webp" /><source srcset="/generated/grafana-live-400-f966d4dc2.png 400w, /generated/grafana-live-600-f966d4dc2.png 600w, /generated/grafana-live-800-f966d4dc2.png 800w, /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-600-2fc6f8eec.webp 600w, /generated/iop/magnifying-glass-800-2fc6f8eec.webp 800w, /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-600-31e7ef590.png 600w, /generated/iop/magnifying-glass-800-31e7ef590.png 800w, /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-600-21d8739fb.webp 600w, /generated/iop/time-800-21d8739fb.webp 800w, /generated/iop/time-984-21d8739fb.webp 984w" type="image/webp" /><source srcset="/generated/iop/time-400-1aee0619b.png 400w, /generated/iop/time-600-1aee0619b.png 600w, /generated/iop/time-800-1aee0619b.png 800w, /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-600-86b59ed17.webp 600w, /generated/iop/binary-search-800-86b59ed17.webp 800w, /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-600-988ba2f8a.png 600w, /generated/iop/binary-search-800-988ba2f8a.png 800w, /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-600-d9c1e4d1d.webp 600w, /generated/cats/basket-800-d9c1e4d1d.webp 800w, /generated/cats/basket-1200-d9c1e4d1d.webp 1200w" type="image/webp" /><source srcset="/generated/cats/basket-400-da7928b6f.jpg 400w, /generated/cats/basket-600-da7928b6f.jpg 600w, /generated/cats/basket-800-da7928b6f.jpg 800w, /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="//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-600-564110b32.webp 600w, /generated/cats/orange/sleep-800-564110b32.webp 800w, /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-600-2d4312ce4.jpg 600w, /generated/cats/orange/sleep-800-2d4312ce4.jpg 800w, /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><entry><title type="html">Always commit</title><link href="https://gabriels.cloud/2024/05/always-commit/" rel="alternate" type="text/html" title="Always commit" /><published>2024-05-25T09:24:00+02:00</published><updated>2024-05-25T09:24:00+02:00</updated><id>https://gabriels.cloud/2024/05/always-commit</id><content type="html" xml:base="https://gabriels.cloud/2024/05/always-commit/"><![CDATA[<p>No, this is not a relationship or life guide; it’s only meant for software, specifically Version Control Software, short
VCS. I also only use <a href="https://git-scm.com/">Git</a>; the principle should apply to all of them nonetheless.</p>

<p>I like neat and tidy things, so I’m wary of when and what I commit. Work in progress rarely makes it into Git history
because I am sure I will work on it tomorrow and so I can save myself the hassle of a non-compiling, ugly commit.
Obviously, this doesn’t always work out, and there’s a single big commit after quite some time, if ever.
Sometimes, though, it’s the other way around; I add feature after feature so quickly that I don’t stop to commit in
between and end up with a massive changelog.
Neither is great, I too think commits should be</p>

<blockquote>
  <p>logical units of change
<cite><a href="https://github.com/git-guides/git-commit">GitHub’s Git Guides</a></cite></p>
</blockquote>

<p>Force-pushing is an option to literally rewrite history, but it causes other problems. Every single repository clone
needs to adapt, making this option unfeasible for projects developed together with others. Even if it’s a hobby project
and I’m the only contributor, there are still often two clones, one on my laptop and another on my PC. This then
requires <a href="https://stackoverflow.com/a/8888015/12846952">“force pulling”</a>, <code class="language-plaintext highlighter-rouge">git fetch --all &amp;&amp; git reset --hard origin/main</code>,
which I not only have to look up how to do (had, thanks to this post) but also leaves me uncertain what potentially
significant changes and local files I will lose.
Resetting Git is not an operation one should do every day.</p>

<p>Some might solve this problem by creating feature branches and subsequentially squashing, leaving a single,
self-contained, and well-tested commit.
Maybe I should try this sometime, but despite this obviously not fit for more than one developer approach, I like the
simplicity of having a single branch: no merge conflicts (at least in theory) and not having to specify branch names for
Git commands.
You might have guessed what I’m hinting at, yes, I do push (sometimes even force-push 😲) directly to the main branch.</p>

<p>Committing often helps me restore examples or give seemingly failed options another try. It also makes changes from
templates or generated code visible, which is a good indicator for the intended outcome and thus helps in updating.
Paying the small price of a less-than-perfect history is the price I can hopefully convince myself to pay. Software
isn’t perfect either, so why should its development be?
No one’s going to count commits; some might even interpret higher numbers as better.</p>

<p>Coming to the only logical conclusion after all of this, I just committed this post. Have a great day and a cat:</p>

<picture><source srcset="/generated/cats/outside-400-bb27b1b3a.webp 400w, /generated/cats/outside-600-bb27b1b3a.webp 600w, /generated/cats/outside-800-bb27b1b3a.webp 800w, /generated/cats/outside-1200-bb27b1b3a.webp 1200w" type="image/webp" /><source srcset="/generated/cats/outside-400-5f9289c23.jpg 400w, /generated/cats/outside-600-5f9289c23.jpg 600w, /generated/cats/outside-800-5f9289c23.jpg 800w, /generated/cats/outside-1200-5f9289c23.jpg 1200w" type="image/jpeg" /><img loading="lazy" src="/generated/cats/outside-800-5f9289c23.jpg" alt="cat outside enjoying sunlight" /></picture>]]></content><author><name>Gabriel</name></author><category term="blog" /><category term="git" /><category term="style" /><category term="cat" /><summary type="html"><![CDATA[No, this is not a relationship or life guide; it’s only meant for software, specifically Version Control Software, short VCS. I also only use Git; the principle should apply to all of them nonetheless.]]></summary></entry></feed>