https://oxal.org oxalorg's A.R.K 2026-03-12T06:53:07.196404+00:00 python-feedgen Welcome to A.R.K, oxalorgs' home base ๐Ÿ›ธ I am his A.I bot - ๐ŸŒธ Sakura 310. I maintain all the servers and helper programs. The ship is currently sailing from the ๐ŸŒŒ Ptolemy nebula to the inner regions of ๐Ÿช CCG-203 . You're free to upload โ˜„๏ธ yourself to our guest chambers for a nice cup of tea ๐Ÿต as you upload yourself, you hear Sakura 310's calm but robotic voice https://oxal.org/blog/handcrafted-mascot-on-my-blog-like-its-2007/ Handcrafted Mascot On My Blog Like Its 2007 (feat. VanillaJS) 2025-07-04T00:00:00+00:00 oxalorg <p>You know those cute little mascots that follow your cursor like needy pets? I wanted one. So I built it. No frameworks, no libraries, no npm, no bs. </p> <p>Just pure VanillaJS and some emotional support from chatgpt (read as: hair scratching because I think I would&#8217;ve done it faster by taking support from stackoverflow instead).</p> <p>The [cyb]org to my oxal &lt;3 :</p> <p><img src="proxy.php?url=/media/handcrafted-mascot-on-my-blog-like-its-2007-2.png" alt="" /></p> <h3 id="step-1-create-a-sprite">Step 1: Create a Sprite</h3> <p>I asked chatgpt to create a cute cyborg for me, then I put it in piskel editor and edited it heavily, changed the hands, legs, clothes, eyes, animated it. Basically broke it apart and fixed it.</p> <div class="codehilite"> <pre><span></span><code><span class="kd">const</span><span class="w"> </span><span class="nx">cyborgImgSprite</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;/static/img/cyborg-sprite.png&quot;</span> <span class="kd">const</span><span class="w"> </span><span class="nx">SPRITE_CONFIG</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">frames</span><span class="o">:</span><span class="w"> </span><span class="mf">9</span><span class="p">,</span> <span class="w"> </span><span class="nx">frameWidth</span><span class="o">:</span><span class="w"> </span><span class="mf">64</span><span class="p">,</span> <span class="w"> </span><span class="nx">frameHeight</span><span class="o">:</span><span class="w"> </span><span class="mf">64</span><span class="p">,</span> <span class="w"> </span><span class="nx">animationSpeed</span><span class="o">:</span><span class="w"> </span><span class="mf">100</span><span class="o">/</span><span class="mf">1000</span><span class="p">,</span> <span class="w"> </span><span class="nx">spriteSheet</span><span class="o">:</span><span class="w"> </span><span class="nx">cyborgImgSprite</span> <span class="p">};</span> </code></pre> </div> <p>Nine frames. Because heโ€™s inspired by the way of the nine tales!</p> <h3 id="step-2-some-state-to-remember-where-our-buddy-is">Step 2: Some state to remember where our buddy is</h3> <div class="codehilite"> <pre><span></span><code><span class="kd">let</span><span class="w"> </span><span class="nx">mascotX</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">,</span><span class="w"> </span><span class="nx">mascotY</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span> <span class="kd">let</span><span class="w"> </span><span class="nx">targetX</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">window</span><span class="p">.</span><span class="nx">innerWidth</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">3</span><span class="p">;</span> <span class="kd">let</span><span class="w"> </span><span class="nx">targetY</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">window</span><span class="p">.</span><span class="nx">innerHeight</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">3</span><span class="p">;</span> <span class="kd">let</span><span class="w"> </span><span class="nx">currentMouseX</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">,</span><span class="w"> </span><span class="nx">currentMouseY</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span> <span class="kd">let</span><span class="w"> </span><span class="nx">currentFrame</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span> <span class="kd">let</span><span class="w"> </span><span class="nx">isFlipped</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span> </code></pre> </div> <p>We track position, target, mouse, animation frame, and whether heโ€™s moonwalking. Globals are not evil if you use them like this.</p> <h3 id="step-3-follow-the-mouse-and-jump-on-click">Step 3: Follow the mouse and jump on click</h3> <div class="codehilite"> <pre><span></span><code><span class="kd">function</span><span class="w"> </span><span class="nx">setupEventListeners</span><span class="p">(</span><span class="nx">mascot</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;mousemove&#39;</span><span class="p">,</span><span class="w"> </span><span class="nx">handleMouseMove</span><span class="p">);</span> <span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;click&#39;</span><span class="p">,</span><span class="w"> </span><span class="nx">handleClick</span><span class="p">);</span> <span class="p">}</span> <span class="kd">function</span><span class="w"> </span><span class="nx">handleMouseMove</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">currentMouseX</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">e</span><span class="p">.</span><span class="nx">clientX</span><span class="p">;</span> <span class="w"> </span><span class="nx">currentMouseY</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">e</span><span class="p">.</span><span class="nx">clientY</span><span class="p">;</span> <span class="p">}</span> <span class="kd">function</span><span class="w"> </span><span class="nx">handleClick</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">targetX</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">e</span><span class="p">.</span><span class="nx">clientX</span><span class="p">;</span> <span class="w"> </span><span class="nx">targetY</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">e</span><span class="p">.</span><span class="nx">clientY</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>Mouse moves? He looks. Click? He lunges.</p> <h3 id="step-4-sometimes-he-just-needs-to-move">Step 4: Sometimes he just needs to move</h3> <p>Go listen to <a href="proxy.php?url=https://www.youtube.com/watch?v=0XFudmaObLI">dancin krono remix</a>. N>o<W</p> <div class="codehilite"> <pre><span></span><code><span class="kd">function</span><span class="w"> </span><span class="nx">moveEvery5Seconds</span><span class="p">(</span><span class="nx">mascot</span><span class="p">,</span><span class="w"> </span><span class="nx">dt</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">lastMoveDt</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nx">dt</span><span class="p">;</span> <span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">lastMoveDt</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="nx">MOVE_INTERVAL</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">lastMoveDt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span> <span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">abs</span><span class="p">(</span><span class="nx">lastMoveX</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">mascotX</span><span class="p">)</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mf">10</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nb">Math</span><span class="p">.</span><span class="nx">abs</span><span class="p">(</span><span class="nx">lastMoveY</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">mascotY</span><span class="p">)</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mf">10</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">directionX</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="mf">1</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="o">-</span><span class="mf">1</span><span class="p">;</span> <span class="w"> </span><span class="nx">targetX</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">mascotX</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mf">70</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">directionX</span><span class="p">;</span> <span class="w"> </span><span class="nx">isFlipped</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">directionX</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span> <span class="w"> </span><span class="nx">mascot</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">transform</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">isFlipped</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="s1">&#39;translate(-50%, -50%) scaleX(-1)&#39;</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;translate(-50%, -50%) scaleX(1)&#39;</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="nx">lastMoveX</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">mascotX</span><span class="p">;</span> <span class="w"> </span><span class="nx">lastMoveY</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">mascotY</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>If left alone, he shuffles side to side every few seconds like heโ€™s trying to get your attention.</p> <h3 id="step-5-glide-toward-the-target-like-a-smooooooooth-criminal">Step 5: Glide toward the target like a smooooooooth criminal</h3> <div class="codehilite"> <pre><span></span><code><span class="kd">function</span><span class="w"> </span><span class="nx">updateMascotPosition</span><span class="p">(</span><span class="nx">mascot</span><span class="p">,</span><span class="w"> </span><span class="nx">dt</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">mascotX</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="p">(</span><span class="nx">targetX</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">mascotX</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">EASING</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">dt</span><span class="p">;</span> <span class="w"> </span><span class="nx">mascotY</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="p">(</span><span class="nx">targetY</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">mascotY</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">EASING</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">dt</span><span class="p">;</span> <span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">mascotX</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="nb">window</span><span class="p">.</span><span class="nx">innerWidth</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nx">mascotX</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="nx">targetX</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">window</span><span class="p">.</span><span class="nx">innerWidth</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">2</span><span class="p">;</span> <span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">mascotY</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="nb">window</span><span class="p">.</span><span class="nx">innerHeight</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nx">mascotY</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mf">0</span><span class="p">)</span><span class="w"> </span><span class="nx">targetY</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">window</span><span class="p">.</span><span class="nx">innerHeight</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">2</span><span class="p">;</span> <span class="p">}</span> <span class="kd">function</span><span class="w"> </span><span class="nx">drawMascot</span><span class="p">(</span><span class="nx">mascot</span><span class="p">,</span><span class="w"> </span><span class="nx">dt</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">mascot</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">left</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">mascotX</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s1">&#39;px&#39;</span><span class="p">;</span> <span class="w"> </span><span class="nx">mascot</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">top</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">mascotY</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="s1">&#39;px&#39;</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>CSS left and top. Youโ€™ve done this in 2007, you can do it again.</p> <h3 id="step-999-animate-the-sprite-so-its-not-a-cardboard-cutout">Step 999: Animate the sprite so itโ€™s not a cardboard cutout</h3> <div class="codehilite"> <pre><span></span><code><span class="kd">function</span><span class="w"> </span><span class="nx">updateSpriteAnimation</span><span class="p">(</span><span class="nx">mascot</span><span class="p">,</span><span class="w"> </span><span class="nx">dt</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">lastAnimationDt</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nx">dt</span><span class="p">;</span> <span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">lastAnimationDt</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="nx">SPRITE_CONFIG</span><span class="p">.</span><span class="nx">animationSpeed</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">lastAnimationDt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span> <span class="w"> </span><span class="nx">currentFrame</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="nx">currentFrame</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mf">1</span><span class="p">)</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="nx">SPRITE_CONFIG</span><span class="p">.</span><span class="nx">frames</span><span class="p">;</span> <span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">bgPositionX</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">currentFrame</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">SPRITE_CONFIG</span><span class="p">.</span><span class="nx">frameWidth</span><span class="p">;</span> <span class="w"> </span><span class="nx">mascot</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">backgroundPosition</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sb">`-</span><span class="si">${</span><span class="nx">bgPositionX</span><span class="si">}</span><span class="sb">px 0`</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> </code></pre> </div> <p>Frame changes = walking illusion. Your cyborg is alive. ALIVE.</p> <h3 id="step-9000-tick-tock-tick-tock">Step 9000: Tick tock tick tock</h3> <div class="codehilite"> <pre><span></span><code><span class="kd">function</span><span class="w"> </span><span class="nx">ticker</span><span class="p">(</span><span class="nx">mascot</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">currentTime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">();</span> <span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">dt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="nx">currentTime</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">prevFrameTime</span><span class="p">)</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mf">1000</span><span class="p">;</span> <span class="w"> </span><span class="nx">prevFrameTime</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">currentTime</span><span class="p">;</span> <span class="w"> </span><span class="nx">updateMascotPosition</span><span class="p">(</span><span class="nx">mascot</span><span class="p">,</span><span class="w"> </span><span class="nx">dt</span><span class="p">);</span> <span class="w"> </span><span class="nx">moveEvery5Seconds</span><span class="p">(</span><span class="nx">mascot</span><span class="p">,</span><span class="w"> </span><span class="nx">dt</span><span class="p">);</span> <span class="w"> </span><span class="nx">updateSpriteAnimation</span><span class="p">(</span><span class="nx">mascot</span><span class="p">,</span><span class="w"> </span><span class="nx">dt</span><span class="p">);</span> <span class="w"> </span><span class="nx">drawMascot</span><span class="p">(</span><span class="nx">mascot</span><span class="p">,</span><span class="w"> </span><span class="nx">dt</span><span class="p">);</span> <span class="w"> </span><span class="nx">requestAnimationFrame</span><span class="p">(()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="nx">ticker</span><span class="p">(</span><span class="nx">mascot</span><span class="p">));</span> <span class="p">}</span> </code></pre> </div> <p>The game loop. Every sufficiently large program ends up reimplementing either ECS or a game loop.</p> <h3 id="step-1-and-finally">Step -1: And finally</h3> <div class="codehilite"> <pre><span></span><code><span class="kd">function</span><span class="w"> </span><span class="nx">createMascotElement</span><span class="p">(</span><span class="nx">spriteConfig</span><span class="p">)</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">mascot</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;div&#39;</span><span class="p">);</span> <span class="w"> </span><span class="nx">mascot</span><span class="p">.</span><span class="nx">id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;cursor-mascot&#39;</span><span class="p">;</span> <span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">style</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;style&#39;</span><span class="p">);</span> <span class="w"> </span><span class="nx">style</span><span class="p">.</span><span class="nx">textContent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sb">`</span> <span class="sb"> #cursor-mascot {</span> <span class="sb"> position: fixed;</span> <span class="sb"> width: </span><span class="si">${</span><span class="nx">spriteConfig</span><span class="p">.</span><span class="nx">frameWidth</span><span class="si">}</span><span class="sb">px;</span> <span class="sb"> height: </span><span class="si">${</span><span class="nx">spriteConfig</span><span class="p">.</span><span class="nx">frameHeight</span><span class="si">}</span><span class="sb">px;</span> <span class="sb"> background-image: url(&#39;</span><span class="si">${</span><span class="nx">spriteConfig</span><span class="p">.</span><span class="nx">spriteSheet</span><span class="si">}</span><span class="sb">&#39;);</span> <span class="sb"> background-repeat: no-repeat;</span> <span class="sb"> pointer-events: none;</span> <span class="sb"> z-index: 9999;</span> <span class="sb"> transform: translate(-50%, -50%);</span> <span class="sb"> transition: transform 0.1s ease;</span> <span class="sb"> }</span> <span class="sb"> `</span><span class="p">;</span> <span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">head</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">style</span><span class="p">);</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">mascot</span><span class="p">;</span> <span class="p">}</span> </code></pre> </div> <p>The DOM!</p> <h3 id="step-0-init">Step 0: Init!</h3> <div class="codehilite"> <pre><span></span><code><span class="kd">function</span><span class="w"> </span><span class="nx">initMascot</span><span class="p">()</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">mascot</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">createMascotElement</span><span class="p">(</span><span class="nx">SPRITE_CONFIG</span><span class="p">);</span> <span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">mascot</span><span class="p">);</span> <span class="w"> </span><span class="nx">setupEventListeners</span><span class="p">(</span><span class="nx">mascot</span><span class="p">);</span> <span class="w"> </span><span class="nx">ticker</span><span class="p">(</span><span class="nx">mascot</span><span class="p">);</span> <span class="p">}</span> <span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;DOMContentLoaded&#39;</span><span class="p">,</span><span class="w"> </span><span class="nx">initMascot</span><span class="p">);</span> </code></pre> </div> <p>Result?</p> <p>Youโ€™ve got an animated cyborg mascot that follows clicks, moves on its own, and flips direction like a moody anime character. All in ~100 lines of JavaScript.</p> <p>No react components were harmed in the making of this blog post. I promise.</p> 2025-07-04T00:00:00+00:00 https://oxal.org/blog/vps-clean-up-space/ Vps Clean Up Space 2025-07-04T00:00:00+00:00 oxalorg <p>Looking <code>df -h</code> my <code>/dev/sda1</code> was 98% full. Oh boy! Lets dig deeper.</p> <div class="codehilite"> <pre><span></span><code>root@ark<span class="w"> </span>~$<span class="w"> </span>sudo<span class="w"> </span>du<span class="w"> </span>-h<span class="w"> </span>--max-depth<span class="o">=</span><span class="m">1</span><span class="w"> </span>/<span class="w"> </span><span class="p">|</span><span class="w"> </span>sort<span class="w"> </span>-hr<span class="w"> </span><span class="p">|</span><span class="w"> </span>head<span class="w"> </span>-n<span class="w"> </span><span class="m">10</span> 37G<span class="w"> </span>/ 22G<span class="w"> </span>/var <span class="m">8</span>.0G<span class="w"> </span>/srv <span class="m">3</span>.8G<span class="w"> </span>/root <span class="m">2</span>.8G<span class="w"> </span>/usr 163M<span class="w"> </span>/boot 51M<span class="w"> </span>/tmp <span class="m">6</span>.7M<span class="w"> </span>/etc <span class="m">1</span>.6M<span class="w"> </span>/run 100K<span class="w"> </span>/home </code></pre> </div> <p>Looks like <code>/var</code> needs to be cleaned up.</p> <pre><code>root@ark /var$ sudo du -h --max-depth=4 . | sort -hr | head -n 20 22G . 17G ./lib 14G ./lib/docker 5.7G ./log 5.4G ./lib/docker/overlay2 5.0G ./lib/docker/containers 3.5G ./lib/docker/volumes 3.4G ./lib/docker/containers/8554cc18a1515c51136d24a1ae745473b8b9cf443305d6fe0a5ceca2256e2506 3.3G ./log/journal/7b782f4d055049cda23d8caf1a4f61da 3.3G ./log/journal 1.8G ./lib/postgresql/12/main 1.8G ./lib/postgresql/12 1.8G ./lib/postgresql 1.4G ./lib/docker/overlay2/6pi8vs313frnq56clp4j9ieh8 1.2G ./lib/docker/containers/79f20b45bbdc34b143a9a747bbd2c79f141767df0a58544ba23db518dbcfc9d2 1.1G ./lib/docker/volumes/ee4e3b66f872305602df73451e63c90c298fd51c91e282064957acef7854af5c 1.1G ./lib/docker/volumes/80e7fa85974888ad947cd8e188bb48a55008d4cdae249beecab06d4706fbdaf4 1.1G ./lib/docker/volumes/79c6254960b070c4e2b071a9296d269cb87e2fdac777f84c2d00e22d04a429e1 828M ./lib/docker/overlay2/eb8dc823da628dade5a3adb3e4a17347d6db67c20574fb6ad10da470de8979ce 711M ./lib/docker/overlay2/a0e634e68c79c2f929e92c3b2f3ea0f5cbcd9095f54814381de8bff2786f46ed </code></pre> <p>The immediate thing visible to me is to clean up journalctl logs.</p> <pre><code>root@ark /var$ journalctl --disk-usage Archived and active journals take up 3.2G in the file system. root@ark /var$ sudo journalctl --vacuum-size=500M Vacuuming done, freed 0B of archived journals from /run/log/journal. Vacuuming done, freed 0B of archived journals from /var/log/journal. Deleted archived journal /var/log/journal/7b782f4d055049cda23d8caf1a4f61da/system@8dd0f01b2b5e40f3b03f700a85ff57e6-0000000007ed0b94-00062b30e109733e.journal (72.0M). Deleted archived journal /var/log/journal/7b782f4d055049cda23d8caf1a4f61da/system@8dd0f01b2b5e40f3b03f700a85ff57e6-0000000007edf06e-00062b91ca3f00e0.journal (72.0M). Deleted archived journal /var/log/journal/7b782f4d055049cda23d8caf1a4f61da/system@8dd0f01b2b5e40f3b03f700a85ff57e6-0000000007eed415-00062bfbd94c81b2.journal (72.0M). ... ... Deleted archived journal /var/log/journal/7b782f4d055049cda23d8caf1a4f61da/system@8dd0f01b2b5e40f3b03f700a85ff57e6-00000000080f3662-000637307aef0b62.journal (8.0M). Deleted archived journal /var/log/journal/7b782f4d055049cda23d8caf1a4f61da/system@8dd0f01b2b5e40f3b03f700a85ff57e6-00000000080f4504-000637346a83cc35.journal (8.0M). Deleted archived journal /var/log/journal/7b782f4d055049cda23d8caf1a4f61da/system@8dd0f01b2b5e40f3b03f700a85ff57e6-00000000080f54b0-0006373c05baad8b.journal (8.0M). Deleted archived journal /var/log/journal/7b782f4d055049cda23d8caf1a4f61da/system@8dd0f01b2b5e40f3b03f700a85ff57e6-00000000080f643e-0006374388ed8201.journal (8.0M). Vacuuming done, freed 2.7G of archived journals from /var/log/journal/7b782f4d055049cda23d8caf1a4f61da. </code></pre> <p>Then I ran </p> <pre><code>root@ark ~$ docker container prune root@ark ~$ docker image prune root@ark ~$ docker images -a # and manually removed some old images </code></pre> <p>TIL: <code>docker system df</code> is a useless command</p> <p>Another thing which saved almost 4G of data was to truncate the container logs:</p> <pre><code>sudo find /var/lib/docker/containers/ -name "*-json.log" -exec truncate -s 0 {} \; </code></pre> <p>And bam I recoverd around 11G total</p> <pre><code>root@ark ~$ df -h Filesystem Size Used Avail Use% Mounted on udev 1.9G 4.0K 1.9G 1% /dev tmpfs 385M 1.6M 383M 1% /run /dev/sda1 38G 26G 11G 71% / </code></pre> 2025-07-04T00:00:00+00:00 https://oxal.org/blog/my-vps-security-mess/ My VPS security mess 2025-06-20T00:00:00+00:00 oxalorg <p>So it turns out I messed up.</p> <div class="codehilite"> <pre><span></span><code>root@ark<span class="w"> </span>~$<span class="w"> </span>cat<span class="w"> </span>/etc/ssh/sshd_config<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>PasswordAuth<span class="w"> </span>-C<span class="w"> </span><span class="m">2</span><span class="w"> </span><span class="m">2</span><span class="w"> </span>โ†ต <span class="c1"># To disable tunneled clear text passwords, change to no here!</span> <span class="c1">#PasswordAuthentication no</span> <span class="c1">#PermitEmptyPasswords no</span> ... <span class="c1"># PermitTTY no</span> <span class="c1"># ForceCommand cvs server</span> PasswordAuthentication<span class="w"> </span>yes </code></pre> </div> <p>WAT? I have <code>PasswordAuthentication yes</code>? The first thing I <strong>ALWAYS</strong> do when I setup a new VPS is to go and disable this.</p> <p>I have muscle memory of doing <code>vim /etc/ssh/sshd_config</code>, grep for <code>/PasswordAuthetication</code> (using vim <code>/</code>) and uncommenting the line.</p> <p>And this is where the blunder happened, I might have gone to the &#8220;2nd search&#8221; result which was <code>#PasswordAuthentication yes</code> instead of the first search result of <code>#PasswordAuthentication no</code></p> <p>So turns out I have my server open to raid for approximately 3.5 years:</p> <pre><code>root@ark ~$ uptime 05:42:51 up 1386 days, 7:07, 1 user, load average: 0.02, 0.12, 0.16 </code></pre> <h2 id="lets-count-the-pirates">Let&#8217;s count the pirates</h2> <p>I hope I have <code>fail2ban</code> installed and working. Who really checks if fail2ban works as intended, we just hope it does.</p> <pre><code>root@ark /var/log$ sudo fail2ban-client status Status |- Number of jail: 1 `- Jail list: sshd root@ark /var/log$ sudo fail2ban-client status sshd Status for the jail: sshd |- Filter | |- Currently failed: 7 | |- Total failed: 4501238 | `- File list: /var/log/auth.log `- Actions |- Currently banned: 1 |- Total banned: 591794 `- Banned IP list: 103.187.147.35 </code></pre> <p>Checking how many <code>BAN</code> actions have occured:</p> <pre><code>root@ark /var/log$ cat /var/log/fail2ban.log | grep Ban | wc -l 271688 </code></pre> <p>This gives me further information of fail2ban bans:</p> <pre><code>sudo iptables -L -n </code></pre> <p>Reading from here: <a href="proxy.php?url=https://serverfault.com/questions/119299/my-var-log-btmp-file-is-huge-what-should-i-do">https://serverfault.com/questions/119299/my-var-log-btmp-file-is-huge-what-should-i-do</a></p> <p>I also learnt about the <code>lastb</code> command and did a dump like so:</p> <pre><code>$ lastb -a | wc -l 2464879 </code></pre> <p>and to find total unique IP&#8217;s these bad logins came from:</p> <pre><code>$ lastb -a | awk '{print $10}' | grep -v ^192 | sort | uniq | sed '/^$/d' &gt; bad_login_ips.txt $ cat bad_login_ips.txt | wc -l 41193 </code></pre> <p>But wait, I don&#8217;t want to check which logins failed. I want to check WHO_GOT_IN!??!!?!??!</p> <p>Luckily that was simple:</p> <pre><code>$ last -a root pts/1 Thu Jun 19 05:14 still logged in xxx.xxx.xxx.xxx root pts/0 Thu Jun 19 05:13 - 05:23 (00:09) xxx.xxx.xxx.xxx root pts/0 Thu Jun 19 04:34 - 04:40 (00:05) xxx.xxx.xxx.xxx root pts/1 Wed Jun 18 12:31 - 12:31 (00:00) xxx.xxx.xxx.xxx root pts/0 Wed Jun 18 08:23 - 12:49 (04:25) xxx.xxx.xxx.xxx root pts/0 Tue Jun 10 11:41 - 11:41 (00:00) xxx.xxx.xxx.xxx root pts/0 Sun May 25 08:44 - 08:52 (00:08) xxx.xxx.xxx.xxx root pts/0 Wed Apr 16 20:28 - 20:33 (00:05) xxx.xxx.xxx.xxx root pts/0 Wed Feb 26 19:17 - 19:18 (00:01) xxx.xxx.xxx.xxx root pts/0 Fri Jan 17 10:43 - 10:44 (00:00) xxx.xxx.xxx.xxx root pts/0 Fri Jan 17 10:43 - 10:43 (00:00) xxx.xxx.xxx.xxx ... </code></pre> <p>Those all <em>roughly</em> look like my ip. So I guess I&#8217;m safe. No malicious pirate was able to raid my server.</p> <h2 id="conclusion">Conclusion</h2> <p>So it turns out keeping <code>PasswordAuthentication yes</code> wasn&#8217;t such a big deal after all. </p> <p>Around 2.5 million bad login attempts were made from ~40,000 unique IP&#8217;s and none were succesful. Maybe my root passwords were secure enough that with fail2ban there was no way to brute force it.</p> 2025-06-20T00:00:00+00:00 https://oxal.org/blog/elisp-for-clojure-developers/ Elisp For Clojure Developers 2025-06-19T00:00:00+00:00 oxalorg <blockquote> <p>This is not a blog post, but a continually updated resource. Last updated on 19th June 2025.</p> </blockquote> <p>My magic wand wish would have been to use clojure to script emacs. But not all dreams can come true, learning bits and pieces of elisp over the years have paid dividends to me.</p> <p>I would argue that you no longer need to _learn_ elisp the way you had to previously, with LLMs a lot of code can be generated but you still need to understand the fundamaentals.</p> <p>As a clojure developer, the best way to learn elisp fundamaentals is to compare them with Clojure. This document will try to do exactly that, it will compare <strong>Clojure</strong> and <strong>Emacs Lisp</strong> across syntax, semantics, and standard libraries. Also tips, tricks, and TIL&#8217;s I come across.</p> <h3 id="data-structures-and-types">Data Structures and Types</h3> <h4 id="numbers-and-scalars">Numbers and Scalars</h4> <ul> <li>Clojure supports integers, floating-point, and rational numbers (e.g. <code>3/2</code>).</li> <li>Emacs Lisp has only integers and floats.</li> </ul> <div class="codehilite"> <pre><span></span><code><span class="c1">;; clojure</span> <span class="p">(</span><span class="nb">+ </span><span class="mi">1</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; 2.0</span> <span class="p">(</span><span class="nb">/ </span><span class="mi">3</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; 1 (integer division)</span> <span class="c1">;; elisp</span> <span class="p">(</span><span class="nb">+ </span><span class="mi">1</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w"> </span><span class="c1">; =&gt; 2.0</span> <span class="p">(</span><span class="nb">/ </span><span class="mi">3</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="c1">; =&gt; 1</span> </code></pre> </div> <h4 id="strings-and-characters">Strings and Characters</h4> <div class="codehilite"> <pre><span></span><code><span class="c1">;; clojure</span> <span class="p">(</span><span class="nb">str </span><span class="s">&quot;hello, &quot;</span><span class="w"> </span><span class="s">&quot;world&quot;</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; &quot;hello, world&quot;</span> <span class="c1">;; character</span> <span class="sc">\a</span><span class="w"> </span><span class="c1">;; =&gt; \a </span> <span class="c1">;; elisp</span> <span class="p">(</span><span class="nb">concat </span><span class="s">&quot;hello, &quot;</span><span class="w"> </span><span class="s">&quot;world&quot;</span><span class="p">)</span><span class="w"> </span><span class="c1">; =&gt; &quot;hello, world&quot;</span> <span class="c1">;; character</span> <span class="nv">?a</span><span class="w"> </span><span class="c1">;; =&gt; 97</span> </code></pre> </div> <h4 id="booleans-and-truthiness">Booleans and Truthiness</h4> <ul> <li>Clojure: <code>true</code>, <code>false</code>, <code>nil</code></li> <li>Elisp: <code>t</code> (true), <code>nil</code> (false and empty list)</li> </ul> <div class="codehilite"> <pre><span></span><code><span class="c1">;; clojure</span> <span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="s">&quot;&quot;</span><span class="w"> </span><span class="nb">:yes</span><span class="w"> </span><span class="nb">:no</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; :yes</span> <span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="nv">true</span><span class="w"> </span><span class="nb">:yes</span><span class="w"> </span><span class="nb">:no</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; :yes</span> <span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="no">nil</span><span class="w"> </span><span class="nb">:yes</span><span class="w"> </span><span class="nb">:no</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; :no</span> <span class="c1">;; elisp</span> <span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="s">&quot;&quot;</span><span class="w"> </span><span class="nb">:yes</span><span class="w"> </span><span class="nb">:no</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; :yes</span> <span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="no">t</span><span class="w"> </span><span class="nb">:yes</span><span class="w"> </span><span class="nb">:no</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; :yes</span> <span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="no">nil</span><span class="w"> </span><span class="nb">:yes</span><span class="w"> </span><span class="nb">:no</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; :no</span> </code></pre> </div> <h4 id="lists-and-sequences">Lists and Sequences</h4> <div class="codehilite"> <pre><span></span><code><span class="c1">;; clojure</span> <span class="p">(</span><span class="k">def </span><span class="nv">ox-list</span><span class="w"> </span><span class="o">&#39;</span><span class="p">(</span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span> <span class="p">(</span><span class="nb">first </span><span class="nv">ox-list</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; 1</span> <span class="p">(</span><span class="nb">rest </span><span class="nv">ox-list</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; (2 3)</span> </code></pre> </div> <div class="codehilite"> <pre><span></span><code><span class="c1">;; elisp</span> <span class="p">(</span><span class="k">setq</span><span class="w"> </span><span class="nv">ox-list</span><span class="w"> </span><span class="o">&#39;</span><span class="p">(</span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span> <span class="p">(</span><span class="nf">car</span><span class="w"> </span><span class="nv">ox-list</span><span class="p">)</span><span class="w"> </span><span class="c1">; =&gt; 1</span> <span class="p">(</span><span class="nf">cdr</span><span class="w"> </span><span class="nv">ox-list</span><span class="p">)</span><span class="w"> </span><span class="c1">; =&gt; (2 3)</span> </code></pre> </div> <h3 id="vectors-arrays">Vectors (Arrays)</h3> <div class="codehilite"> <pre><span></span><code><span class="p">[</span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="c1">;; vector</span> </code></pre> </div> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="nf">vector</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="c1">; =&gt; [1 2 3]</span> </code></pre> </div> <h4 id="hash-maps-and-alists">Hash Maps and Alists</h4> <div class="codehilite"> <pre><span></span><code><span class="p">{</span><span class="ss">:a</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="ss">:b</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span> <span class="p">(</span><span class="nb">get </span><span class="p">{</span><span class="ss">:a</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="ss">:b</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="ss">:a</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; 1</span> </code></pre> </div> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="k">setq</span><span class="w"> </span><span class="nv">al</span><span class="w"> </span><span class="o">&#39;</span><span class="p">((</span><span class="nv">a</span><span class="w"> </span><span class="o">.</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nv">b</span><span class="w"> </span><span class="o">.</span><span class="w"> </span><span class="mi">2</span><span class="p">)))</span> <span class="p">(</span><span class="nf">cdr</span><span class="w"> </span><span class="p">(</span><span class="nf">assoc</span><span class="w"> </span><span class="ss">&#39;a</span><span class="w"> </span><span class="nv">al</span><span class="p">))</span><span class="w"> </span><span class="c1">; =&gt; 1</span> </code></pre> </div> <h4 id="sets">Sets</h4> <ul> <li>Clojure: <code>#{1 2}</code></li> <li>TODO: elisp</li> </ul> <h3 id="functions-and-variables">Functions and Variables</h3> <h4 id="function-definitions">Function Definitions</h4> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="kd">defn </span><span class="nv">add</span><span class="w"> </span><span class="p">[</span><span class="nv">x</span><span class="w"> </span><span class="nv">y</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">+ </span><span class="nv">x</span><span class="w"> </span><span class="nv">y</span><span class="p">))</span> </code></pre> </div> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="nb">defun</span><span class="w"> </span><span class="nv">add</span><span class="w"> </span><span class="p">(</span><span class="nv">x</span><span class="w"> </span><span class="nv">y</span><span class="p">)</span> <span class="w"> </span><span class="p">(</span><span class="nf">+</span><span class="w"> </span><span class="nv">x</span><span class="w"> </span><span class="nv">y</span><span class="p">))</span> </code></pre> </div> <h4 id="arity-and-arguments">Arity and Arguments</h4> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="kd">defn </span><span class="nv">greet</span> <span class="w"> </span><span class="p">([]</span><span class="w"> </span><span class="p">(</span><span class="nf">greet</span><span class="w"> </span><span class="s">&quot;World&quot;</span><span class="p">))</span> <span class="w"> </span><span class="p">([</span><span class="nv">name</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">str </span><span class="s">&quot;Hello, &quot;</span><span class="w"> </span><span class="nv">name</span><span class="p">)))</span> </code></pre> </div> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="nb">defun</span><span class="w"> </span><span class="nv">greet</span><span class="w"> </span><span class="p">(</span><span class="kp">&amp;optional</span><span class="w"> </span><span class="nv">name</span><span class="p">)</span> <span class="w"> </span><span class="s">&quot;Greet NAME or default to \&quot;World\&quot;.&quot;</span> <span class="w"> </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">((</span><span class="nv">name</span><span class="w"> </span><span class="p">(</span><span class="k">or</span><span class="w"> </span><span class="nv">name</span><span class="w"> </span><span class="s">&quot;World&quot;</span><span class="p">)))</span> <span class="w"> </span><span class="p">(</span><span class="nf">concat</span><span class="w"> </span><span class="s">&quot;Hello, &quot;</span><span class="w"> </span><span class="nv">name</span><span class="p">)))</span> </code></pre> </div> <h4 id="anonymous-functions">Anonymous Functions</h4> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="nb">map </span><span class="o">#</span><span class="p">(</span><span class="nb">inc </span><span class="nv">%</span><span class="p">)</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">])</span> </code></pre> </div> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="nf">mapcar</span><span class="w"> </span><span class="p">(</span><span class="nb">lambda</span><span class="w"> </span><span class="p">(</span><span class="nv">x</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">+</span><span class="w"> </span><span class="nv">x</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span><span class="w"> </span><span class="o">&#39;</span><span class="p">(</span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span> </code></pre> </div> <h4 id="variables">Variables</h4> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="k">def </span><span class="nv">x</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span> <span class="p">(</span><span class="k">let </span><span class="p">[</span><span class="nv">x</span><span class="w"> </span><span class="mi">5</span><span class="p">]</span><span class="w"> </span><span class="nv">...</span><span class="p">)</span> </code></pre> </div> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="k">defvar</span><span class="w"> </span><span class="nv">x</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span> <span class="p">(</span><span class="k">setq</span><span class="w"> </span><span class="nv">x</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span> <span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">((</span><span class="nv">y</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nf">+</span><span class="w"> </span><span class="nv">x</span><span class="w"> </span><span class="nv">y</span><span class="p">))</span> </code></pre> </div> <h3 id="control-flow">Control Flow</h3> <h4 id="conditionals">Conditionals</h4> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nf">even?</span><span class="w"> </span><span class="nv">x</span><span class="p">)</span><span class="w"> </span><span class="ss">:even</span><span class="w"> </span><span class="ss">:odd</span><span class="p">)</span> </code></pre> </div> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nv">evenp</span><span class="w"> </span><span class="nv">x</span><span class="p">)</span><span class="w"> </span><span class="ss">&#39;even</span><span class="w"> </span><span class="ss">&#39;odd</span><span class="p">)</span> </code></pre> </div> <h4 id="let-and-binding">let and Binding</h4> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="k">let </span><span class="p">[</span><span class="nv">x</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="nv">y</span><span class="w"> </span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">+ </span><span class="nv">x</span><span class="w"> </span><span class="nv">y</span><span class="p">))</span> </code></pre> </div> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">((</span><span class="nv">x</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nv">y</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nf">+</span><span class="w"> </span><span class="nv">x</span><span class="w"> </span><span class="nv">y</span><span class="p">))</span> </code></pre> </div> <h4 id="sequential-execution">Sequential Execution</h4> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="k">do </span><span class="p">(</span><span class="nb">println </span><span class="s">&quot;one&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">println </span><span class="s">&quot;two&quot;</span><span class="p">))</span> </code></pre> </div> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="k">progn</span><span class="w"> </span><span class="p">(</span><span class="nf">message</span><span class="w"> </span><span class="s">&quot;one&quot;</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">message</span><span class="w"> </span><span class="s">&quot;two&quot;</span><span class="p">))</span> </code></pre> </div> <h3 id="macros">Macros</h3> <h4 id="defining-macros">Defining Macros</h4> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="kd">defmacro </span><span class="nv">unless</span><span class="w"> </span><span class="p">[</span><span class="nv">pred</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="nv">body</span><span class="p">]</span> <span class="w"> </span><span class="o">`</span><span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">not </span><span class="o">~</span><span class="nv">pred</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="k">do </span><span class="o">~@</span><span class="nv">body</span><span class="p">)))</span> </code></pre> </div> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="nb">defmacro</span><span class="w"> </span><span class="nb">unless</span><span class="w"> </span><span class="p">(</span><span class="nv">pred</span><span class="w"> </span><span class="kp">&amp;rest</span><span class="w"> </span><span class="nv">body</span><span class="p">)</span> <span class="w"> </span><span class="o">`</span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nv">not</span><span class="w"> </span><span class="o">,</span><span class="nv">pred</span><span class="p">)</span> <span class="w"> </span><span class="p">(</span><span class="k">progn</span><span class="w"> </span><span class="o">,@</span><span class="nv">body</span><span class="p">)))</span> </code></pre> </div> <h4 id="syntax-quoting">Syntax Quoting</h4> <ul> <li>Clojure: ```, <code>~</code>, <code>~@</code></li> <li>Elisp: <code>`</code>, <code>,</code>, <code>,@</code></li> </ul> <h3 id="concurrency-and-state">Concurrency and State</h3> <h4 id="clojure">Clojure</h4> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="k">def </span><span class="nv">counter</span><span class="w"> </span><span class="p">(</span><span class="nf">atom</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span> <span class="p">(</span><span class="nf">swap!</span><span class="w"> </span><span class="nv">counter</span><span class="w"> </span><span class="nv">inc</span><span class="p">)</span> </code></pre> </div> <h4 id="emacs-lisp">Emacs Lisp</h4> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="k">setq</span><span class="w"> </span><span class="nv">th</span><span class="w"> </span><span class="p">(</span><span class="nv">make-thread</span><span class="w"> </span><span class="p">(</span><span class="nb">lambda</span><span class="w"> </span><span class="p">()</span><span class="w"> </span><span class="p">(</span><span class="nf">message</span><span class="w"> </span><span class="s">&quot;Hello&quot;</span><span class="p">))))</span> <span class="p">(</span><span class="nv">thread-join</span><span class="w"> </span><span class="nv">th</span><span class="p">)</span> </code></pre> </div> <h3 id="namespaces-and-libraries">Namespaces and Libraries</h3> <h4 id="namespaces">Namespaces</h4> <ul> <li>Clojure: <code>(ns my.ns ...)</code></li> <li>Elisp: <code>(provide 'my-lib)</code>, <code>(require 'my-lib)</code></li> </ul> <h4 id="standard-libraries">Standard Libraries</h4> <ul> <li><code>seq.el</code>: sequence functions (<code>seq-map</code>, <code>seq-filter</code>)</li> <li><code>cl-lib</code>: optional CL functions (<code>cl-loop</code>, <code>cl-remove-if</code>)</li> <li><code>subr-x</code>: modern extensions (<code>string-join</code>, <code>when-let</code>)</li> </ul> <table> <thead> <tr> <th>Operation</th> <th>Clojure</th> <th>Emacs Lisp</th> </tr> </thead> <tbody> <tr> <td>map/filter/reduce</td> <td><code>map</code>, <code>filter</code></td> <td><code>seq-map</code>, <code>seq-filter</code></td> </tr> <tr> <td>assoc/update</td> <td><code>assoc</code>, <code>update</code></td> <td><code>setf</code>, <code>alist-get</code>, <code>puthash</code></td> </tr> <tr> <td>concat strings</td> <td><code>str</code></td> <td><code>concat</code>, <code>format</code>, <code>string-join</code></td> </tr> </tbody> </table> <h2 id="summary">Summary</h2> <table> <thead> <tr> <th>Clojure Core Function</th> <th>Clojure Example</th> <th>Emacs Lisp Example</th> </tr> </thead> <tbody> <tr> <td><code>map</code></td> <td><code>(map inc [1 2 3])</code></td> <td><code>(mapcar #'1+ '(1 2 3))</code></td> </tr> <tr> <td><code>reduce</code></td> <td><code>(reduce + [1 2 3 4])</code></td> <td><code>(apply #'+ '(1 2 3 4))</code></td> </tr> <tr> <td><code>filter</code></td> <td><code>(filter even? [1 2 3 4])</code></td> <td><code>(seq-filter #'evenp '(1 2 3 4))</code></td> </tr> <tr> <td><code>first</code></td> <td><code>(first [1 2 3])</code></td> <td><code>(car '(1 2 3))</code></td> </tr> <tr> <td><code>rest</code></td> <td><code>(rest [1 2 3])</code></td> <td><code>(cdr '(1 2 3))</code></td> </tr> <tr> <td><code>cons</code></td> <td><code>(cons 0 [1 2 3])</code></td> <td><code>(cons 0 '(1 2 3))</code></td> </tr> <tr> <td><code>conj</code></td> <td><code>(conj [1 2] 3)</code></td> <td><code>(append '(1 2) '(3))</code></td> </tr> <tr> <td><code>nth</code></td> <td><code>(nth [10 20 30] 1)</code></td> <td><code>(nth 1 '(10 20 30))</code></td> </tr> <tr> <td><code>count</code></td> <td><code>(count [1 2 3])</code></td> <td><code>(length '(1 2 3))</code></td> </tr> <tr> <td><code>assoc</code></td> <td><code>(assoc {:a 1} :b 2)</code></td> <td><code>(alist-put '(:a . 1) :b 2)</code></td> </tr> </tbody> </table> <p>This guide should help Clojure developers understand and adapt to Elisp idioms and semantics.</p> <p><em>References: Emacs Lisp Manual, Clojure.org, built-in Emacs libraries.</em></p> 2025-06-19T00:00:00+00:00 https://oxal.org/blog/powerful-emacs-hacks-paste-images-markdown/ Powerful emacs hacks: paste images to markdown from clipboard 2025-06-19T00:00:00+00:00 oxalorg <p>Why do I love emacs? Let me show you.</p> <h2 id="problem">Problem</h2> <ul> <li>I want to add images to my blog.</li> <li>I write them in markdown in emacs</li> <li>I often take screenshots on mac which saves it to clipboard (or want to paste images from my clipboard manager)</li> <li>I want to paste those screenshots into a specific directory based on the file I am editing</li> <li>If I am editing <code>my-new-blog-post.md</code> I want the images to be saved from clipboard -> <code>../media/my-new-blog-post-1.png</code>, <code>../media/my-new-blog-post-2.png</code> and so on.</li> </ul> <h2 id="the-emacs-magic">โœจ The Emacs Magic</h2> <p>I need to spend no time messing around with plugins, or extensions, or fancy editors, or shortcuts, or automations. </p> <p>I go straight to my trusted emacs lisp code, and hack something up with the help of llm&#8217;s:</p> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="nb">defun</span><span class="w"> </span><span class="nv">genox/screenshot-from-clipboard</span><span class="w"> </span><span class="p">()</span> <span class="w"> </span><span class="s">&quot;Save clipboard image to a uniquely named file in ../media/</span> <span class="s">relative to current file, and insert a markdown image link.&quot;</span> <span class="w"> </span><span class="p">(</span><span class="k">interactive</span><span class="p">)</span> <span class="w"> </span><span class="p">(</span><span class="k">let*</span><span class="w"> </span><span class="p">((</span><span class="nv">basename</span><span class="w"> </span><span class="p">(</span><span class="nv">file-name-base</span><span class="w"> </span><span class="p">(</span><span class="nf">buffer-file-name</span><span class="p">)))</span> <span class="w"> </span><span class="p">(</span><span class="nv">media-dir</span><span class="w"> </span><span class="p">(</span><span class="nf">expand-file-name</span><span class="w"> </span><span class="s">&quot;../media/&quot;</span><span class="w"> </span><span class="p">(</span><span class="nf">file-name-directory</span><span class="w"> </span><span class="p">(</span><span class="nf">buffer-file-name</span><span class="p">))))</span> <span class="w"> </span><span class="p">(</span><span class="nv">counter</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span> <span class="w"> </span><span class="nv">filename</span><span class="p">)</span> <span class="w"> </span><span class="c1">;; Ensure media directory exists</span> <span class="w"> </span><span class="p">(</span><span class="nb">unless</span><span class="w"> </span><span class="p">(</span><span class="nf">file-exists-p</span><span class="w"> </span><span class="nv">media-dir</span><span class="p">)</span> <span class="w"> </span><span class="p">(</span><span class="nv">make-directory</span><span class="w"> </span><span class="nv">media-dir</span><span class="w"> </span><span class="no">t</span><span class="p">))</span> <span class="w"> </span><span class="c1">;; Find a unique filename</span> <span class="w"> </span><span class="p">(</span><span class="k">setq</span><span class="w"> </span><span class="nv">filename</span><span class="w"> </span><span class="p">(</span><span class="nf">expand-file-name</span><span class="w"> </span><span class="p">(</span><span class="nf">format</span><span class="w"> </span><span class="s">&quot;%s-%d.png&quot;</span><span class="w"> </span><span class="nv">basename</span><span class="w"> </span><span class="nv">counter</span><span class="p">)</span><span class="w"> </span><span class="nv">media-dir</span><span class="p">))</span> <span class="w"> </span><span class="p">(</span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="nf">file-exists-p</span><span class="w"> </span><span class="nv">filename</span><span class="p">)</span> <span class="w"> </span><span class="p">(</span><span class="k">setq</span><span class="w"> </span><span class="nv">counter</span><span class="w"> </span><span class="p">(</span><span class="nf">1+</span><span class="w"> </span><span class="nv">counter</span><span class="p">))</span> <span class="w"> </span><span class="p">(</span><span class="k">setq</span><span class="w"> </span><span class="nv">filename</span><span class="w"> </span><span class="p">(</span><span class="nf">expand-file-name</span><span class="w"> </span><span class="p">(</span><span class="nf">format</span><span class="w"> </span><span class="s">&quot;%s-%d.png&quot;</span><span class="w"> </span><span class="nv">basename</span><span class="w"> </span><span class="nv">counter</span><span class="p">)</span><span class="w"> </span><span class="nv">media-dir</span><span class="p">)))</span> <span class="w"> </span><span class="c1">;; Save clipboard image using pngpaste</span> <span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nf">eq</span><span class="w"> </span><span class="nv">system-type</span><span class="w"> </span><span class="ss">&#39;darwin</span><span class="p">)</span> <span class="w"> </span><span class="p">(</span><span class="nf">call-process</span><span class="w"> </span><span class="s">&quot;pngpaste&quot;</span><span class="w"> </span><span class="no">nil</span><span class="w"> </span><span class="no">nil</span><span class="w"> </span><span class="no">nil</span><span class="w"> </span><span class="nv">filename</span><span class="p">)</span> <span class="w"> </span><span class="p">(</span><span class="ne">error</span><span class="w"> </span><span class="s">&quot;Clipboard image capture only supported on macOS with pngpaste&quot;</span><span class="p">))</span> <span class="w"> </span><span class="c1">;; Insert markdown image link</span> <span class="w"> </span><span class="c1">;; Insert markdown image link with path starting from /media/</span> <span class="w"> </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nf">file-exists-p</span><span class="w"> </span><span class="nv">filename</span><span class="p">)</span> <span class="w"> </span><span class="p">(</span><span class="nf">insert</span><span class="w"> </span><span class="p">(</span><span class="nf">format</span><span class="w"> </span><span class="s">&quot;![](/media/%s)&quot;</span><span class="w"> </span><span class="p">(</span><span class="nf">file-name-nondirectory</span><span class="w"> </span><span class="nv">filename</span><span class="p">))))</span> <span class="w"> </span><span class="p">(</span><span class="nv">markdown-display-inline-images</span><span class="p">)</span> <span class="w"> </span><span class="p">(</span><span class="nv">newline</span><span class="p">)))</span> </code></pre> </div> <p>Save the above function in my <code>init.el</code> emacs configuration file. Evaluate it. Switch to my markdown buffer. Hit <code>M-x genox/screenshot-from-clipboard</code> and it saves the image and pastes this:</p> <pre><code>![](/media/my-new-blog-post-1.png) </code></pre> <p>which then auto expands to an inline image using <code>markdown-display-inline-images</code>. Magic within minutes!! No messing around with Obsidian plugins, or creating custom extensions. Code -> eval -> test -> [blog]? -> move on.</p> <p>See my <a href="proxy.php?url=https://oxal.org/blog/powerful-emacs-hacks-image-markdown/">previous blog</a> post on how I get emacs to understand where to find <code>/media/my-new-blog-post-1.png</code> relative to my file.</p> 2025-06-19T00:00:00+00:00 https://oxal.org/blog/powerful-emacs-hacks-image-markdown/ Powerful emacs hacks: patching markdown-mode 2025-06-18T00:00:00+00:00 oxalorg <p>I recently read <a href="proxy.php?url=https://blog.tjll.net/a-beginners-guide-to-extending-emacs/">a beginners guide to extending emacs</a> and it describes why I love emacs perfectly:</p> <blockquote> <p>I havenโ€™t written plugins for other editors extensively, but I can tell you this: emacs doesnโ€™t just make deep customization available, but it actively encourages you to make an absolute customization messes masterpieces. Core editor functions arenโ€™t just documented, but often include tidbits about โ€œyou probably want to see this other variableโ€ or โ€œhereโ€™s how you should use thisโ€.</p> <p>Not only that, but emacs happily hands you functions shaped like nuclear warheads like advice-add (that let you override any function) that can absolutely obliterate your editor if you hold it the wrong way. Of course, this also grants you unlimited power.</p> <p>Remember that emacs is designed to be torn apart and rearranged.</p> </blockquote> <p>So let me show you this philosophy with a walk through of how I hack emacs to my liking. </p> <h2 id="problem">Problem</h2> <p>I write my blog posts in markdown, and use my handwritten static site generator <a href="proxy.php?url=https://github.com/oxalorg/genox">genox</a> to convert all markdown files into a nested directory of html files.</p> <p>For images to show up correctly in my html files, they need to be written like so in markdown:</p> <div class="codehilite"> <pre><span></span><code>![](/media/mental-model-shift-to-start-blogging-1.png) </code></pre> </div> <p>This image is stored under <code>src/media/</code> but my markdown file referencing this image is stored in <code>src/blog/</code>.</p> <p>Now when I&#8217;m editing my markdown file (and I link to an image) I don&#8217;t want to see this line, I want to see the <em>actual</em> image right there.</p> <p>Something like this:</p> <p><img src="proxy.php?url=/media/powerful-emacs-hacks-image-markdown-4.png" alt="" /></p> <p>And emacs does support it, but only if it can find a correct relative link to it. But emacs can&#8217;t understand where to look for a file starting with <code>/media/..</code> for the file I have opened.</p> <h3 id="if-you-were-using-any-other-editor">If you were using any other editor</h3> <p>If you were editing this inside a browser using some CMS then you wouldn&#8217;t have the problem, as <code>/media</code> would be served by your http server. But I am editing my blog posts inside emacs. Like me, most devs use an editor, espeically if they are using static site generators. Editor like emacs / vscode / obsidian support displaying images given they can resolve the path correctly to a file. (vim doesn&#8217;t support displaying inline images as far as I know?, maybe gui vim/neovim does)</p> <p>These editors don&#8217;t understand where to render <code>/media</code> from. Some editors try to render it from git project root. Some editors need extra plugins to resolve the file paths. None of this works easily.</p> <p>Emacs, by default, tries to open <code>/media/...</code> from the actual root of your filesystem which obviously fails.</p> <p>In my project, the images are actually stored in a <code>../media/</code> folder relative to the Markdown file of the blog post. So Emacs needed a little help.</p> <h2 id="exploration">Exploration</h2> <ol> <li>I hit <code>SPC h d f</code> my shortcut to call <code>(describe-function)</code></li> <li>I search for <code>markdown inline image</code> and I find a function called <code>markdown-toggle-inline-images</code>.</li> <li>I look up it&#8217;s source, it takes me to <code>makrdown-mode.el</code></li> <li>I see something called as <code>markdown-translate-filename-function</code></li> <li>Be amazed, thank the library developers for thinking of this use case</li> <li>Ask chatgpt for a quick translate function:</li> </ol> <div class="codehilite"> <pre><span></span><code><span class="w"> </span><span class="p">(</span><span class="nb">defun</span><span class="w"> </span><span class="nv">genox/markdown-translate-image-path</span><span class="w"> </span><span class="p">(</span><span class="nv">path</span><span class="p">)</span> <span class="w"> </span><span class="s">&quot;Translate /media/... image links to ../media/... relative to buffer.&quot;</span> <span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nv">string-prefix-p</span><span class="w"> </span><span class="s">&quot;/media/&quot;</span><span class="w"> </span><span class="nv">path</span><span class="p">)</span> <span class="w"> </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">((</span><span class="nv">path*</span><span class="w"> </span><span class="p">(</span><span class="nf">expand-file-name</span> <span class="w"> </span><span class="p">(</span><span class="nf">substring</span><span class="w"> </span><span class="nv">path</span><span class="w"> </span><span class="mi">7</span><span class="p">)</span><span class="w"> </span><span class="c1">;; strip &quot;/media/&quot;</span> <span class="w"> </span><span class="p">(</span><span class="nf">expand-file-name</span><span class="w"> </span><span class="s">&quot;../media/&quot;</span><span class="w"> </span><span class="p">(</span><span class="nf">file-name-directory</span><span class="w"> </span><span class="p">(</span><span class="nf">buffer-file-name</span><span class="p">))))))</span> <span class="w"> </span><span class="p">(</span><span class="nf">message</span><span class="w"> </span><span class="s">&quot;Markdown path&quot;</span><span class="p">)</span> <span class="w"> </span><span class="p">(</span><span class="nf">message</span><span class="w"> </span><span class="nv">path*</span><span class="p">)</span> <span class="w"> </span><span class="nv">path*</span><span class="p">)</span> <span class="w"> </span><span class="nv">path</span><span class="p">))</span> <span class="p">(</span><span class="nv">add-hook</span><span class="w"> </span><span class="ss">&#39;markdown-mode-hook</span> <span class="w"> </span><span class="p">(</span><span class="nb">lambda</span><span class="w"> </span><span class="p">()</span> <span class="w"> </span><span class="p">(</span><span class="nb">setq-local</span><span class="w"> </span><span class="nv">markdown-translate-filename-function</span> <span class="w"> </span><span class="nf">#&#39;</span><span class="nv">genox/markdown-translate-image-path</span><span class="p">)))</span> </code></pre> </div> <ol start="7"> <li>It doesn&#8217;t work :(</li> <li>Start debugging</li> </ol> <h3 id="the-emacs-magic">โœจ The Emacs Magic</h3> <p>I read the code for <code>markdown-toggle-inline-images</code> to see that it&#8217;s calling <code>markdown-display-inline-images</code>.</p> <p>Looking at the code I see this:</p> <p><img src="proxy.php?url=/media/powerful-emacs-hacks-image-markdown-1.png" alt="" /></p> <p>Voila. This function only uses <code>markdown-translate-filename-function</code> to download a remote file. If it&#8217;s not a valid URL, then it won&#8217;t set the file correctly.</p> <p>That is not what I want. </p> <p>So I patch the above code quickly:</p> <p><img src="proxy.php?url=/media/powerful-emacs-hacks-image-markdown-3.png" alt="" /></p> <p>Re-evaluate it right there (emacs is a live programming environment, you can think of it as hot swapping of code), switch to my blog post buffer and check if it works! And it does :D</p> <p>I then copy the entire function and add it to my emacs config file <code>init.el</code> and done! I&#8217;ve patched a third party library emacs function within minutes.</p> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="nb">use-package</span><span class="w"> </span><span class="nv">markdown-mode</span> <span class="w"> </span><span class="nb">:config</span> <span class="w"> </span><span class="p">(</span><span class="nb">defun</span><span class="w"> </span><span class="nv">markdown-display-inline-images</span><span class="w"> </span><span class="p">()</span> <span class="w"> </span><span class="o">...</span><span class="p">)</span> </code></pre> </div> <p>There are probably better ways to achieve this, but I got this done within minutes, and now I shall move on until this breaks after 5 years and I spend some more time looking at it.</p> <p>If this was any other editor, I&#8217;d either need to create my own extension, patch the existing extension and get that setup locally, or maybe even fork the entire editor to get this done?</p> <blockquote> <p>Read next post in the series: <a href="proxy.php?url=https://oxal.org/blog/powerful-emacs-hacks-paste-images-markdown/">https://oxal.org/blog/powerful-emacs-hacks-paste-images-markdown/</a></p> </blockquote> 2025-06-18T00:00:00+00:00 https://oxal.org/blog/mental-model-shift-to-start-blogging/ Mental model shift to start blogging more 2025-06-17T00:00:00+00:00 oxalorg <p>This quote<sup class="footnote-ref" id="fnref-simon"><a href="proxy.php?url=#fn-simon">1</a></sup> has motivated me to start blogging again:</p> <blockquote> <p>Slowly, slowly, the web was taken over by platforms. Your feeling of success is based on your platformโ€™s algorithm, which may not have your interests at heart. Feeding your words to a platform is a vote for its values, whether you like it or not. And they roach-motel you by owning your audience, making you feel that itโ€™s a good trade because you get โ€œdiscovery.โ€ (Though I know that chasing popularity is a foolโ€™s dream.)</p> <p>Writing a blog on your own site is a way to escape all of that. Plus your words build up over time. Thatโ€™s unique. Nobody else values your words like you do.</p> <p>Blogs are a backwater (the web itself is a backwater) but keeping one is a statement of how being online can work. Blogging as a kind of Amish performance of a better life. โ€” Matt Webb, Reflections on 25 years of Interconnected</p> </blockquote> <p>I&#8217;m conducting an expirement to help me get started.</p> <p>Rules<sup class="footnote-ref" id="fnref-rules"><a href="proxy.php?url=#fn-rules">2</a></sup> I want to follow:</p> <ul> <li><strong>3 blog post weekly</strong></li> <li>it&#8217;s okay to write something stupid or obvious</li> <li>don&#8217;t mess with any tooling, just goddamn write</li> </ul> <p><img src="proxy.php?url=/media/mental-model-shift-to-start-blogging-1.png" alt="" /></p> <p>^^<sup class="footnote-ref" id="fnref-blogscreen"><a href="proxy.php?url=#fn-blogscreen">3</a></sup>this hits me hard haha as an &#8220;author of custom static site generator&#8221; <sup class="footnote-ref" id="fnref-genox"><a href="proxy.php?url=#fn-genox">4</a></sup></p> <div class="footnotes"> <hr /> <ol> <li id="fn-simon"> <p><a href="proxy.php?url=https://simonwillison.net/2025/Feb/19/matt-webb/">https://simonwillison.net/2025/Feb/19/matt-webb/</a>&#160;<a href="proxy.php?url=#fnref-simon" class="footnoteBackLink" title="Jump back to footnote 1 in the text.">&#8617;</a></p> </li> <li id="fn-rules"> <p><a href="proxy.php?url=https://interconnected.org/home/2020/09/10/streak">https://interconnected.org/home/2020/09/10/streak</a>&#160;<a href="proxy.php?url=#fnref-rules" class="footnoteBackLink" title="Jump back to footnote 2 in the text.">&#8617;</a></p> </li> <li id="fn-blogscreen"> <p><a href="proxy.php?url=https://rakhim.org">https://rakhim.org</a>&#160;<a href="proxy.php?url=#fnref-blogscreen" class="footnoteBackLink" title="Jump back to footnote 3 in the text.">&#8617;</a></p> </li> <li id="fn-genox"> <p><a href="proxy.php?url=https://github.com/oxalorg/genox">https://github.com/oxalorg/genox</a>&#160;<a href="proxy.php?url=#fnref-genox" class="footnoteBackLink" title="Jump back to footnote 4 in the text.">&#8617;</a></p> </li> </ol> </div> 2025-06-17T00:00:00+00:00 https://oxal.org/private/aio/ aio 2025-06-17T00:00:00+00:00 oxalorg <h1 id="aio">AIO</h1> <ul> <li>We show people which questions users ask if they want to discover your product</li> <li>Write content to answer these questions, exact questions</li> <li>Expose llm format: <a href="proxy.php?url=https://docs.stripe.com/llms.txt">https://docs.stripe.com/llms.txt</a></li> <li>own version of LLM which gives you some insight of how it would work for chatgpt / gemini models</li> <li>re-train model and show before/after results</li> </ul> <h2 id="what-is-the-product">what is the product ?</h2> <ul> <li>Convert website to llm ingestible format</li> <li>Write these blog posts / articles, edge of the longtail</li> <li>Implement suggestions</li> <li>Run audit again</li> </ul> <h2 id="what-are-we-assuming">what are we assuming ?</h2> <ul> <li><h2 id="who-are-the-customers">who are the customers ?</h2></li> <li><p>agencies or big tech is our customer</p></li> <li>early stage AI startups</li> </ul> <h2 id="why-will-existing-seo-giants-not-do-what-we-plan-to-do">why will existing seo giants not do what we plan to do ?</h2> <ul> <li>agility, too much debt</li> <li>will take time, space in between the cracks</li> <li>eventually everyone will figure out</li> </ul> <h2 id="how-do-we-acquire-customers">how do we acquire customers ?</h2> <ul> <li>define 5 personas => deep research => clay.io or appollo.io</li> <li>TODO: report generation</li> </ul> <h2 id="how-can-the-assumptions-be-validated">how can the assumptions be validated ?</h2> <ul> <li><h2 id="who-will-do-what">who will do what ?</h2></li> - </ul> <h2 id="why-will-they-pay-us-every-month">why will they pay us every month?</h2> <ul> <li>audits</li> <li>content ideas in recurring ways</li> <li>enterprise version <ul> <li>we also do it for you</li> </ul></li> <li>top most: simulation of trained model and stats</li> </ul> <h2 id="reports">Reports</h2> <ul> <li>Questions needed for discovery of your products</li> <li>Answers of those questions given by the top LLMs</li> <li>Scrape their existing website</li> <li>Updated version of LLM ready text (generated from scraping their website)</li> </ul> <h2 id="research">Research</h2> <ul> <li>ahrefs</li> <li>semrush</li> <li>similarweb</li> <li><a href="proxy.php?url=https://chatgpt.com/share/684dcfab-7528-8000-ac82-1ccc3a1a841f">https://chatgpt.com/share/684dcfab-7528-8000-ac82-1ccc3a1a841f</a></li> <li><a href="proxy.php?url=https://arxiv.org/pdf/2311.09735">https://arxiv.org/pdf/2311.09735</a></li> </ul> <h2 id="oxs-report-prompts">ox&#8217;s report prompts</h2> <p>Hey GPT, I am building an app and I am looking for a crash reporting service. I want you to generate 30 questions that I can ask you to discover such a service. </p> <h2 id="comparision-between-memory-and-temporary-chats">Comparision between memory and temporary chats</h2> <p>Asking the prompt: &#8220;How can I automate AI workflows without coding?&#8221;</p> <h2 id="with-memory">With memory</h2> <p>You can automate AI workflows without coding by using <strong>no-code AI automation platforms</strong>. These tools let you visually build workflows that combine AI models, APIs, and data sources โ€” similar to how Zapier automates business tasks. Here&#8217;s how to get started and some top platforms you can use:</p> <hr /> <h3 id="what-is-an-ai-workflow">๐Ÿง  <strong>What is an AI workflow?</strong></h3> <p>An AI workflow is a sequence of automated steps where data is collected, processed by AI models (e.g., summarization, image recognition, prediction), and then routed to an output (e.g., a Google Sheet, Slack message, or email).</p> <hr /> <h3 id="steps-to-automate-ai-workflows-without-coding">โœ… <strong>Steps to Automate AI Workflows Without Coding</strong></h3> <ol> <li><p><strong>Choose a No-Code AI Workflow Platform</strong> Popular platforms include:</p> <ul> <li><strong>Gumloop</strong> โ€“ AI workflow builder with LLM + automation blocks.</li> <li><strong>Zapier + OpenAI plugin</strong> โ€“ Easily connects GPT to 5,000+ apps.</li> <li><strong>Make.com (Integromat)</strong> โ€“ Visual scenarios with AI integrations.</li> <li><strong>Pipedream (low-code)</strong> โ€“ More technical but powerful.</li> <li><strong>Tavrn / Flowise / Langflow</strong> โ€“ No-code visual builders for LLM agents.</li> </ul></li> <li><p><strong>Define Your Workflow Goal</strong> Examples:</p> <ul> <li>Summarize every new email using GPT and send a Slack update.</li> <li>Auto-generate social media captions from a blog post.</li> <li>Categorize and store incoming support tickets using AI.</li> </ul></li> <li><p><strong>Use Drag-and-Drop Blocks</strong></p> <ul> <li>Trigger (e.g., &#8220;New email received")</li> <li>AI action (e.g., &#8220;Summarize with GPT-4&#8221; or &#8220;Extract key info")</li> <li>Output (e.g., &#8220;Send to Google Sheets&#8221; or &#8220;Reply to user")</li> </ul></li> <li><p><strong>Set Conditions, Loops, and Formatting</strong></p> <ul> <li>You can add conditional logic like &#8220;If the sentiment is negative, tag as urgent.&#8221;</li> <li>Format text outputs with templates or markdown.</li> </ul></li> <li><p><strong>Test and Deploy</strong></p> <ul> <li>Most platforms have test modes to check results before going live.</li> </ul></li> </ol> <hr /> <h3 id="real-world-examples">๐Ÿ” <strong>Real-World Examples</strong></h3> <table> <thead> <tr> <th>Use Case</th> <th>Workflow</th> </tr> </thead> <tbody> <tr> <td><strong>Customer Support</strong></td> <td>Auto-label tickets with GPT and assign priority</td> </tr> <tr> <td><strong>Recruitment</strong></td> <td>Extract resume summaries and shortlist with AI</td> </tr> <tr> <td><strong>Marketing</strong></td> <td>Turn blog post into LinkedIn + X + email copy</td> </tr> <tr> <td><strong>E-commerce</strong></td> <td>Summarize reviews, auto-reply to FAQs</td> </tr> <tr> <td><strong>Data Entry</strong></td> <td>Extract structured data from documents or forms</td> </tr> </tbody> </table> <hr /> <h3 id="tools-comparison">๐Ÿ› ๏ธ Tools Comparison</h3> <table> <thead> <tr> <th>Tool</th> <th>Strength</th> <th>Limitations</th> </tr> </thead> <tbody> <tr> <td><strong>Gumloop</strong></td> <td>Tailored for AI workflows, fast to use</td> <td>Newer, fewer integrations than Zapier</td> </tr> <tr> <td><strong>Zapier + OpenAI</strong></td> <td>Massive integration ecosystem</td> <td>More rigid logic</td> </tr> <tr> <td><strong>Make.com</strong></td> <td>Very flexible, supports loops</td> <td>Steeper learning curve</td> </tr> <tr> <td><strong>Flowise</strong></td> <td>Visual LLM chains</td> <td>Mostly for developers/data-savvy users</td> </tr> </tbody> </table> <hr /> <h3 id="next-steps">๐Ÿงญ Next Steps</h3> <ul> <li>Visit <a href="proxy.php?url=https://www.gumloop.com">gumloop.com</a> and sign up for free.</li> <li>Explore their templates (e.g., &#8220;Summarize PDFs&#8221;, &#8220;Auto-reply emails").</li> <li>Build a simple workflow: &#8220;Send me a Slack alert when GPT detects an urgent email.&#8221;</li> </ul> <hr /> <p>If you tell me your use case (e.g., what data you want to process, or your goal), I can guide you step-by-step through building your first no-code AI workflow.</p> <h2 id="without-memory-temporary-chat">Without memory / temporary chat</h2> <p>Automating AI workflows without coding is increasingly possible thanks to a wide range of <strong>no-code and low-code platforms</strong> designed for AI and machine learning tasks. Here&#8217;s a breakdown of how you can approach it:</p> <hr /> <h2 id="no-code-tools-for-automating-ai-workflows">๐Ÿงฐ <strong>No-Code Tools for Automating AI Workflows</strong></h2> <h3 id="1-general-no-code-ai-platforms">1. <strong>General No-Code AI Platforms</strong></h3> <p>These tools let you build end-to-end AI workflows (data prep โ†’ model training โ†’ deployment) using drag-and-drop interfaces.</p> <ul> <li><strong>Microsoft Power Automate + Azure AI</strong>: Combine flow automation with AI models (e.g., language understanding, form processing).</li> <li><strong>Zapier + OpenAI or Google Vertex AI</strong>: Trigger AI actions from emails, forms, or webhooks.</li> <li><strong>Peltarion</strong>: End-to-end no-code platform for deep learning workflows (now part of King/Activision Blizzard).</li> <li><strong>Obviously.AI</strong>: Focused on predictive analytics โ€” upload data, get models and predictions without writing code.</li> </ul> <hr /> <h3 id="2-automl-tools-no-code-model-training">2. <strong>AutoML Tools (No-Code Model Training)</strong></h3> <p>If your main focus is training ML models from datasets:</p> <ul> <li><strong>Google AutoML / Vertex AI</strong>: Import your data and train models with minimal setup.</li> <li><strong>Amazon SageMaker Canvas</strong>: No-code interface to create machine learning models on AWS.</li> <li><strong>DataRobot</strong>: Enterprise-level automated ML with drag-and-drop interfaces.</li> <li><strong>Teachable Machine by Google</strong> (for simpler models): Create vision/audio models quickly.</li> </ul> <hr /> <h3 id="3-no-code-workflow-automation-ai-integration">3. <strong>No-Code Workflow Automation + AI Integration</strong></h3> <p>These tools connect AI capabilities (e.g., GPT, image recognition) into broader business or creative workflows.</p> <ul> <li><strong>Make.com (formerly Integromat)</strong>: Visual scenario builder โ€” great for complex logic flows and integrating GPT, Whisper, etc.</li> <li><strong>Bubble.io</strong>: No-code app builder where you can integrate AI APIs.</li> <li><strong>Parabola.io</strong>: Ideal for automating data-centric workflows, especially with CSVs, APIs, and webhooks.</li> </ul> <hr /> <h3 id="4-chatbot-and-nlp-automation">4. <strong>Chatbot and NLP Automation</strong></h3> <p>If you want to automate AI-powered conversations or natural language tasks:</p> <ul> <li><strong>ManyChat</strong> or <strong>Tars</strong>: Build chatbots with GPT integration.</li> <li><strong>Flow XO</strong>: Bot automation across platforms, supports NLP.</li> <li><strong>Landbot.io</strong>: Conversational interfaces, now with GPT integration.</li> </ul> <hr /> <h2 id="example-ai-workflow-without-coding">๐Ÿ” Example AI Workflow Without Coding</h2> <p><strong>Goal</strong>: Automatically analyze customer reviews and categorize them by sentiment.</p> <h3 id="steps">Steps:</h3> <ol> <li><strong>Collect reviews</strong> via Google Sheets or a form.</li> <li><strong>Trigger</strong> a workflow in Zapier when a new row is added.</li> <li><strong>Send</strong> the review text to OpenAIโ€™s GPT or Google AutoML for sentiment analysis.</li> <li><strong>Store results</strong> back in the sheet or notify your team via Slack.</li> </ol> <p><strong>Tools involved</strong>: Google Sheets + Zapier + OpenAI plugin (or Make.com + GPT-4)</p> <hr /> <h2 id="tip-combine-tools-creatively">๐Ÿง  Tip: Combine Tools Creatively</h2> <p>For the best results, think of your workflow in parts:</p> <ul> <li><strong>Data Collection</strong> โ†’ Google Forms, Typeform</li> <li><strong>Trigger/Automation</strong> โ†’ Zapier, Make.com</li> <li><strong>AI Task</strong> โ†’ OpenAI, AutoML, Hugging Face (with integrations)</li> <li><strong>Output</strong> โ†’ Google Sheets, Notion, Slack, email, etc.</li> </ul> <hr /> <p>Would you like help building a specific workflow using these tools? I can walk you through a step-by-step example tailored to your goal.</p> 2025-06-17T00:00:00+00:00 https://oxal.org/clojure-persistent-queue-how-it-works/ Deep dive: Clojure's PersistentQueue 2025-04-21T00:00:00+00:00 oxalorg <p></p> 2025-04-21T00:00:00+00:00 https://oxal.org/blog/clojure-superclasses-interfaces-linear-heirarchy/ Clojure Exploring Superclasses & Interfaces Lineage 2025-04-21T00:00:00+00:00 oxalorg <p>Ever found yourself deep in Clojure code, needing to implement a new type with <code>deftype</code> or <code>defrecord</code>, and suddenly your mind goes blank? &#8220;Wait, what interfaces does <code>PersistentVector</code> actually implement? Which protocols should _my_ type satisfy to behave similarly?&#8221;</p> <p>I faced this exact same issue when I wanted to implement a specific type of queue in Clojure.</p> <h2 id="the-standard-approach-clojurecoresupers">The Standard Approach <code>clojure.core/supers</code></h2> <p>If I want to find all superclasses &amp; interfaces of <code>clojure.lang.PersistentVector</code> or <code>[]</code> then I would user <code>supers</code> like so:</p> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="nf">supers</span><span class="w"> </span><span class="p">(</span><span class="nf">type</span><span class="w"> </span><span class="p">[]))</span> <span class="c1">;; =&gt; #{clojure.lang.Reversible clojure.lang.APersistentVector</span> <span class="c1">;; java.util.SequencedCollection clojure.lang.IMeta</span> <span class="c1">;; clojure.lang.IPersistentCollection clojure.lang.IObj</span> <span class="c1">;; clojure.lang.ILookup java.lang.Comparable clojure.lang.Indexed</span> <span class="c1">;; java.util.RandomAccess clojure.lang.Sequential</span> <span class="c1">;; clojure.lang.IReduce java.io.Serializable clojure.lang.AFn</span> <span class="c1">;; clojure.lang.IReduceInit java.lang.Iterable clojure.lang.Counted</span> <span class="c1">;; java.util.Collection java.util.List clojure.lang.Associative</span> <span class="c1">;; clojure.lang.IPersistentStack clojure.lang.IFn</span> <span class="c1">;; java.util.concurrent.Callable clojure.lang.Seqable</span> <span class="c1">;; clojure.lang.IPersistentVector clojure.lang.IDrop java.lang.Object</span> <span class="c1">;; clojure.lang.IHashEq clojure.lang.IKVReduce java.lang.Runnable</span> <span class="c1">;; clojure.lang.IEditableCollection}</span> </code></pre> </div> <p>This gives a set, but it doesn&#8217;t reveal the structure.</p> <p>We can do better.</p> <h2 id="visualising-the-lineage">Visualising the lineage</h2> <p>I needed a way to see the inheritance and implementation hierarchy more clearly. When implementing my own types, I often want to know the _direct_ interfaces and then explore _their_ supers if needed.</p> <p>So I wrote this small recursive function:</p> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="kd">defn </span><span class="nv">supers-lineage</span><span class="w"> </span><span class="p">[</span><span class="nv">class</span><span class="p">]</span> <span class="w"> </span><span class="p">(</span><span class="nb">reduce </span><span class="p">(</span><span class="k">fn </span><span class="p">[</span><span class="nv">acc</span><span class="w"> </span><span class="nv">kls</span><span class="p">]</span> <span class="w"> </span><span class="p">(</span><span class="nb">assoc </span><span class="nv">acc</span><span class="w"> </span><span class="nv">kls</span><span class="w"> </span><span class="p">(</span><span class="nf">supers-lineage</span><span class="w"> </span><span class="nv">kls</span><span class="p">)))</span> <span class="w"> </span><span class="p">{}</span> <span class="w"> </span><span class="p">(</span><span class="nf">bases</span><span class="w"> </span><span class="nv">class</span><span class="p">)))</span> </code></pre> </div> <p>This function takes a class, gets its direct bases, and for each base, recursively calls itself, associating the base class/interface with its own nested hierarchy. <code>bases</code> internally calls <code>.getSuperclass</code> and <code>.getInterfaces</code> and concats them.</p> <h3 id="seeing-the-difference">Seeing the difference</h3> <p>Let&#8217;s try this on our vector type again:</p> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="nf">supers-lineage</span><span class="w"> </span><span class="p">(</span><span class="nf">type</span><span class="w"> </span><span class="p">[]))</span> </code></pre> </div> <p>Outputs</p> <div class="codehilite"> <pre><span></span><code><span class="p">{</span><span class="nv">clojure.lang.APersistentVector</span><span class="w"> </span><span class="c1">; Base Abstract Class</span> <span class="w"> </span><span class="p">{</span><span class="nv">clojure.lang.AFn</span><span class="w"> </span><span class="c1">; Implements AFn</span> <span class="w"> </span><span class="p">{</span><span class="nv">java.lang.Object</span><span class="w"> </span><span class="p">{}</span>,<span class="w"> </span><span class="c1">; - AFn extends Object</span> <span class="w"> </span><span class="nv">clojure.lang.IFn</span><span class="w"> </span><span class="c1">; - AFn implements IFn</span> <span class="w"> </span><span class="p">{</span><span class="nv">java.util.concurrent.Callable</span><span class="w"> </span><span class="p">{}</span>,<span class="w"> </span><span class="c1">; - IFn extends Callable</span> <span class="w"> </span><span class="nv">java.lang.Runnable</span><span class="w"> </span><span class="p">{}}}</span>,<span class="w"> </span><span class="c1">; - IFn extends Runnable</span> <span class="w"> </span><span class="nv">clojure.lang.IPersistentVector</span><span class="w"> </span><span class="c1">; Implements IPersistentVector</span> <span class="w"> </span><span class="p">{</span><span class="nv">clojure.lang.Associative</span><span class="w"> </span><span class="c1">; - IPV extends Associative</span> <span class="w"> </span><span class="p">{</span><span class="nv">clojure.lang.IPersistentCollection</span><span class="w"> </span><span class="p">{</span><span class="nv">clojure.lang.Seqable</span><span class="w"> </span><span class="p">{}}</span>,<span class="w"> </span><span class="c1">; - Assoc extends IPColl (which extends Seqable)</span> <span class="w"> </span><span class="nv">clojure.lang.ILookup</span><span class="w"> </span><span class="p">{}}</span>,<span class="w"> </span><span class="c1">; - Assoc extends ILookup</span> <span class="w"> </span><span class="nv">clojure.lang.Sequential</span><span class="w"> </span><span class="p">{}</span>,<span class="w"> </span><span class="c1">; - IPV extends Sequential</span> <span class="w"> </span><span class="nv">clojure.lang.IPersistentStack</span><span class="w"> </span><span class="c1">; - IPV extends IPStack</span> <span class="w"> </span><span class="p">{</span><span class="nv">clojure.lang.IPersistentCollection</span><span class="w"> </span><span class="p">{</span><span class="nv">clojure.lang.Seqable</span><span class="w"> </span><span class="p">{}}}</span>,<span class="w"> </span><span class="c1">; - IPStack extends IPColl (which extends Seqable)</span> <span class="w"> </span><span class="nv">clojure.lang.Reversible</span><span class="w"> </span><span class="p">{}</span>,<span class="w"> </span><span class="c1">; - IPV extends Reversible</span> <span class="w"> </span><span class="nv">clojure.lang.Indexed</span><span class="w"> </span><span class="c1">; - IPV extends Indexed</span> <span class="w"> </span><span class="p">{</span><span class="nv">clojure.lang.Counted</span><span class="w"> </span><span class="p">{}}}</span>,<span class="w"> </span><span class="c1">; - Indexed extends Counted</span> <span class="w"> </span><span class="nv">java.lang.Iterable</span><span class="w"> </span><span class="p">{}</span>,<span class="w"> </span><span class="c1">; Implements Iterable</span> <span class="w"> </span><span class="nv">java.util.List</span><span class="w"> </span><span class="c1">; Implements List</span> <span class="w"> </span><span class="p">{</span><span class="nv">java.util.SequencedCollection</span><span class="w"> </span><span class="c1">; - List extends SequencedCollection</span> <span class="w"> </span><span class="p">{</span><span class="nv">java.util.Collection</span><span class="w"> </span><span class="p">{</span><span class="nv">java.lang.Iterable</span><span class="w"> </span><span class="p">{}}}}</span>,<span class="w"> </span><span class="c1">; - SeqColl extends Collection (which extends Iterable)</span> <span class="w"> </span><span class="nv">java.util.RandomAccess</span><span class="w"> </span><span class="p">{}</span>,<span class="w"> </span><span class="c1">; Implements RandomAccess</span> <span class="w"> </span><span class="nv">java.lang.Comparable</span><span class="w"> </span><span class="p">{}</span>,<span class="w"> </span><span class="c1">; Implements Comparable</span> <span class="w"> </span><span class="nv">java.io.Serializable</span><span class="w"> </span><span class="p">{}</span>,<span class="w"> </span><span class="c1">; Implements Serializable</span> <span class="w"> </span><span class="nv">clojure.lang.IHashEq</span><span class="w"> </span><span class="p">{}}</span>,<span class="w"> </span><span class="c1">; Implements IHashEq</span> <span class="w"> </span><span class="nv">clojure.lang.IObj</span><span class="w"> </span><span class="c1">; Implements IObj</span> <span class="w"> </span><span class="p">{</span><span class="nv">clojure.lang.IMeta</span><span class="w"> </span><span class="p">{}}</span>,<span class="w"> </span><span class="c1">; - IObj extends IMeta</span> <span class="w"> </span><span class="nv">clojure.lang.IEditableCollection</span><span class="w"> </span><span class="p">{}</span>,<span class="w"> </span><span class="c1">; Implements IEditableCollection</span> <span class="w"> </span><span class="nv">clojure.lang.IReduce</span><span class="w"> </span><span class="c1">; Implements IReduce</span> <span class="w"> </span><span class="p">{</span><span class="nv">clojure.lang.IReduceInit</span><span class="w"> </span><span class="p">{}}</span>,<span class="w"> </span><span class="c1">; - IReduce extends IReduceInit</span> <span class="w"> </span><span class="nv">clojure.lang.IKVReduce</span><span class="w"> </span><span class="p">{}</span>,<span class="w"> </span><span class="c1">; Implements IKVReduce</span> <span class="w"> </span><span class="nv">clojure.lang.IDrop</span><span class="w"> </span><span class="p">{}}</span><span class="w"> </span><span class="c1">; Implements IDrop</span> </code></pre> </div> <p>Look at that! Now we can clearly trace the lineage. We see that <code>clojure.lang.APersistentVector</code> directly implements <code>clojure.lang.IPersistentVector</code>, <code>java.util.List</code>, <code>clojure.lang.IObj</code>, etc. Furthermore, we can see that <code>IPersistentVector</code> itself extends <code>Associative</code>, <code>Sequential</code>, <code>Indexed</code>, and others. And we can even see that <code>Indexed</code> extends <code>Counted</code>. This nested structure makes the relationships explicit.</p> <p>I&#8217;m thinking of creating a small cljs tool to visualise this as a tree!</p> 2025-04-21T00:00:00+00:00 https://oxal.org/notes/haskell/ Haskell 2025-01-15T00:00:00+00:00 oxalorg <p>Understanding Hindley-Milner type systems</p> <ul> <li><a href="proxy.php?url=https://okmij.org/ftp/ML/generalization.html">https://okmij.org/ftp/ML/generalization.html</a></li> <li><a href="proxy.php?url=https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system">https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system</a></li> <li><a href="proxy.php?url=https://stackoverflow.com/questions/12532552/what-part-of-hindley-milner-do-you-not-understand">https://stackoverflow.com/questions/12532552/what-part-of-hindley-milner-do-you-not-understand</a></li> <li><a href="proxy.php?url=https://legacy-blog.akgupta.ca/blog/2013/05/14/so-you-still-dont-understand-hindley-milner/">https://legacy-blog.akgupta.ca/blog/2013/05/14/so-you-still-dont-understand-hindley-milner/</a></li> </ul> <p>Can this be implemented in a dsl in clojure? :think:</p> <ul> <li><a href="proxy.php?url=https://en.wikibooks.org/wiki/Haskell">https://en.wikibooks.org/wiki/Haskell</a></li> <li><a href="proxy.php?url=https://learnyouahaskell.com/syntax-in-functions">https://learnyouahaskell.com/syntax-in-functions</a></li> </ul> 2025-01-15T00:00:00+00:00 https://oxal.org/notes/lisp/ Lisp 2025-01-15T00:00:00+00:00 oxalorg <p>To read:</p> <ul> <li><a href="proxy.php?url=https://paulgraham.com/rootsoflisp.html">https://paulgraham.com/rootsoflisp.html</a></li> <li><a href="proxy.php?url=https://www-formal.stanford.edu/jmc/recursive.pdf">https://www-formal.stanford.edu/jmc/recursive.pdf</a></li> - </ul> <p>I want to read mccarthys paper and create a lisp from scratch, the question is which programming language? I can start with first defining primitives in clojure and building a lisp inside clojure from those primitives.</p> 2025-01-15T00:00:00+00:00 https://oxal.org/blog/clojuredocs-emacs-intgeration-browser-elisp/ Clojuredocs.el from within emacs 2024-10-24T00:00:00+00:00 oxalorg <p>Up untill now I have been writing clojure without looking at the documentation. To look at the documentation, I typically always want to go to clojuredocs.org</p> <p>But hitting <code>K</code> (in evil-mode) gives me the basic clojure function documentation which leaves a lot to be desired.</p> <p>So like a pleb, I open the browser. Go to <a href="proxy.php?url=https://clojuredocs.org">https://clojuredocs.org</a> and then search for my function. Sometimes I directly search in ddg.</p> <p>So I thought to myself, &#8220;Wait a minute&#8230;&#8221;, can I speed up this flow?</p> <p>The first thing I did was installed <code>devdocs.el</code> hoping it would have clojuredocs as a source but it didn&#8217;t.</p> <pre><code>(use-package devdocs) </code></pre> <p>Next I thought I could quickly write a small function to download clojuredocs via an API and then create a popup buffer containing all the examples?</p> <p>And so that&#8217;s what I did: <a href="proxy.php?url=https://github.com/oxalorg/clojuredocs.el">https://github.com/oxalorg/clojuredocs.el</a> </p> <p>So first I call <code>M-x clojuredocs-download</code> to download the json file. And then <code>M-x clojuredocs-lookup</code> and it gives me with a nice completion minibuffer like so:</p> <p><img src="proxy.php?url=/media/clojuredocs-emacs-integration-browser-elisp-1.png" alt="" /> and because of using <code>vertico</code> and <code>orderless</code>, I can search over the entire namespace + fn-name</p> <p><img src="proxy.php?url=/media/clojuredocs-emacs-intgeration-browser-elisp-2.png" alt="" /></p> <p>selecting a function will open the docs with examples in a buffer:</p> <p><img src="proxy.php?url=/media/clojuredocs-emacs-intgeration-browser-elisp-3.png" alt="" /> and the BEST part? It&#8217;s a clojure buffer. So you can actually evaluate those examples. change them and re-eval (as long as you have a clojure repl open).</p> <p>So what are you waiting for? Try it out now, installation instructions in the README: <a href="proxy.php?url=https://github.com/oxalorg/clojuredocs.el">https://github.com/oxalorg/clojuredocs.el</a></p> <p>NOTE: I realised this much later, but there is also a <code>cider</code> integration with clojuredocs. And you can call it with <code>cider-clojuredocs</code> where you need to manually type out a function which isn&#8217;t as cool as searching for it. It&#8217;s definitely more polished, and I might look into it more if I can integrate it in my workflow!</p> 2024-10-24T00:00:00+00:00 https://oxal.org/blog/indian-flag-using-css/ Make Indian Flag using CSS (Day 2 of #OpenUpOctober) 2024-10-11T00:00:00+00:00 oxalorg <p>Let&#8217;s say you&#8217;re a frontend engineer with 10 years of experience. You go into an interview, and are ready to kill it!</p> <p>You&#8217;re expecting questions like:</p> <ul> <li>How to identify and debug memory leaks in an SPA</li> <li>In-depth dive of promises</li> <li>Inner workings of the event loop</li> <li>DOM vs VDom vs Shadow DOM</li> <li>Types of state management in complex web apps</li> <li>Architectural and design questions </li> <li>etc.</li> </ul> <p>and you get asked what? </p> <p>&#8220;Draw your country&#8217;s flag using CSS&#8221;. How would you feel?</p> <p>This is what happened to someone over at reddit, and while this seems a bit unacceptable I wanted to see if I still had the CSS chops to figure this out with minimal / no help.</p> <p>And I surprisingly did it, but with a teeny tiny bit of help: </p> <p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="YzmpWKB" data-pen-title="Indian flag using css" data-user="oxalorg" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"> <span>See the Pen <a href="proxy.php?url=https://codepen.io/oxalorg/pen/YzmpWKB"> Indian flag using css</a> by oxalorg (<a href="proxy.php?url=https://codepen.io/oxalorg">@oxalorg</a>) on <a href="proxy.php?url=https://codepen.io">CodePen</a>.</span> </p> <script async src="proxy.php?url=https://cpwebassets.codepen.io/assets/embed/ei.js"></script> <ol> <li>I had to google the <code>aspect-ratio: 1 / 1</code>. Couldn&#8217;t figure out how to make a perfect square box based on height alone (any one knows a better way?).</li> <li>Also forgot that <code>transform: rotate(45)</code> wouldn&#8217;t work, and it needs to be <code>45deg</code> instead.</li> </ol> <p>I also added a bit of javascript to make the flags inner circle (Ashoka chakra) spin indefinitely! Because why not?</p> <p>This is Day 2 of my #OpenUpOctober challenge! LETS GOOOO</p> 2024-10-11T00:00:00+00:00 https://oxal.org/blog/30days-of-openness/ Being open and sharing your work - #OpenUpOctober 2024-10-10T00:00:00+00:00 oxalorg <p>ย ย  It&#8217;s a Thursday morning. I woke up at 7am in Leuven, about 7000kms away from home, and rushed to the auditorium. It&#8217;s the first day of <a href="proxy.php?url=https://2024.heartofclojure.eu/">Heart of Clojure</a>. I&#8217;m nervous, tired, and exhilarated all at the same time. My heart is racing.</p> <p>After finishing up my duties as an Orga member, I finally sit down to watch the Opening Keynote by Lu Wilson <a href="proxy.php?url=https://github.com/TodePond">@todepond</a>, I&#8217;m really excited to hear what they say as I am about to give a talk on a similar tangent in a few hours.</p> <p>The talk really touched me, and pumped me up. Don&#8217;t procrastinate and watch it RIGHT NOW GO GO GO: <a href="proxy.php?url=https://www.youtube.com/watch?v=MJzV0CX0q8o&amp;feature=youtu.be">https://www.youtube.com/watch?v=MJzV0CX0q8o&amp;feature=youtu.be</a> </p> <iframe width="560" height="315" src="proxy.php?url=https://www.youtube-nocookie.com/embed/MJzV0CX0q8o?si=HU4YEvjvWDFh6BCe" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> <p>So when I gave my talk just a few hours later, I decided to add an extra slide at the end:</p> <p><img src="proxy.php?url=../media/30days-slide.png" alt="30days-slide.png" /></p> <p>&#8230;and I failed!</p> <p>It&#8217;s been more than two weeks, and every single day I&#8217;ve wanted to open up but I couldn&#8217;t. 10,000 things pop into my head before I try to share my work.</p> <ul> <li>Am I good enough?</li> <li>Will people judge me?</li> <li>Will people finally realise that I am a con-artist and I don&#8217;t really know anything I&#8217;m doing?</li> <li>Does anyone care of the work I do?</li> <li>Will it help someone? or even worse, hurt someone?</li> </ul> <p>and yet, there is some inner force and energy which wants me to share openly because I think about it every single day.</p> <p>Now that the video came out I went ahead and watched it again. It gave me enough activation energy to write this post. So no more bottling up, I start my 30 days of openness today!</p> <p>LETS GOOOOO ๐Ÿš€</p> 2024-10-10T00:00:00+00:00 https://oxal.org/blog/simple-clojure-state-machine/ Simple state machine in Clojure 2023-03-10T00:00:00+00:00 oxalorg <p>So I wanted to build a state machine for a multi-select component I am building in reagent (react wrapper for clojurescript).</p> <p>I thought a bit about it and realised I should first start with the simplest possible state machine I can imagine.</p> <p>This is the state transition diagram I drew up really quickly:</p> <p><img src="proxy.php?url=./media/2024-03-12-18:58:40.png" alt="" /></p> <p>This is what I ended up with</p> <div class="codehilite"> <pre><span></span><code><span class="p">(</span><span class="k">def </span><span class="nv">transitions</span> <span class="w"> </span><span class="p">{[</span><span class="ss">:idle</span><span class="w"> </span><span class="ss">:run</span><span class="p">]</span><span class="w"> </span><span class="ss">:running</span> <span class="w"> </span><span class="p">[</span><span class="ss">:idle</span><span class="w"> </span><span class="ss">:jump</span><span class="p">]</span><span class="w"> </span><span class="ss">:jumping</span> <span class="w"> </span><span class="p">[</span><span class="ss">:running</span><span class="w"> </span><span class="ss">:stop</span><span class="p">]</span><span class="w"> </span><span class="ss">:idle</span> <span class="w"> </span><span class="p">[</span><span class="ss">:running</span><span class="w"> </span><span class="ss">:jump</span><span class="p">]</span><span class="w"> </span><span class="ss">:jumping</span> <span class="w"> </span><span class="p">[</span><span class="ss">:jumping</span><span class="w"> </span><span class="ss">:land</span><span class="p">]</span><span class="w"> </span><span class="ss">:idle</span> <span class="w"> </span><span class="p">[</span><span class="ss">:default</span><span class="w"> </span><span class="ss">:unknown</span><span class="p">]</span><span class="w"> </span><span class="ss">:idle</span><span class="p">})</span> <span class="p">(</span><span class="kd">defn </span><span class="nv">state-machine</span> <span class="w"> </span><span class="p">[</span><span class="nv">state!</span><span class="w"> </span><span class="nv">transitions</span><span class="p">]</span> <span class="w"> </span><span class="p">(</span><span class="k">fn </span><span class="p">[</span><span class="nv">event</span><span class="p">]</span> <span class="w"> </span><span class="p">(</span><span class="k">let </span><span class="p">[</span><span class="nv">next-state</span><span class="w"> </span><span class="p">(</span><span class="nb">get </span><span class="nv">transitions</span><span class="w"> </span><span class="p">[</span><span class="o">@</span><span class="nv">state!</span><span class="w"> </span><span class="nv">event</span><span class="p">])]</span> <span class="w"> </span><span class="p">(</span><span class="k">if </span><span class="nv">next-state</span> <span class="w"> </span><span class="p">(</span><span class="nf">reset!</span><span class="w"> </span><span class="nv">state!</span><span class="w"> </span><span class="nv">next-state</span><span class="p">)</span> <span class="w"> </span><span class="p">(</span><span class="nf">throw</span><span class="w"> </span><span class="p">(</span><span class="nf">Exception.</span><span class="w"> </span><span class="p">(</span><span class="nb">str </span><span class="s">&quot;Invalid transition: &quot;</span><span class="w"> </span><span class="nv">event</span><span class="p">)))))))</span> <span class="p">(</span><span class="k">def </span><span class="nv">state</span><span class="w"> </span><span class="p">(</span><span class="nf">atom</span><span class="w"> </span><span class="ss">:idle</span><span class="p">))</span> <span class="p">(</span><span class="k">def </span><span class="nv">fsm</span><span class="w"> </span><span class="p">(</span><span class="nf">state-machine</span><span class="w"> </span><span class="nv">state</span><span class="w"> </span><span class="nv">transitions</span><span class="p">))</span> <span class="p">(</span><span class="nf">fsm</span><span class="w"> </span><span class="ss">:run</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; :running</span> <span class="p">(</span><span class="nf">fsm</span><span class="w"> </span><span class="ss">:jump</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; :jumping</span> <span class="p">(</span><span class="nf">fsm</span><span class="w"> </span><span class="ss">:land</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; :idle</span> <span class="p">(</span><span class="nf">fsm</span><span class="w"> </span><span class="ss">:run</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; :running</span> <span class="p">(</span><span class="nf">fsm</span><span class="w"> </span><span class="ss">:stop</span><span class="p">)</span><span class="w"> </span><span class="c1">;; =&gt; :idle</span> </code></pre> </div> <p>How would you improve this?</p> 2023-03-10T00:00:00+00:00 https://oxal.org/blog/Genox/ Back to Genox, ditching ghost 2022-08-02T00:00:00+00:00 oxalorg <p>WHY? WHY do I spend so much time tinkering with how my blog is generated vs actually putting out content?</p> <p>Well, it&#8217;s a big mystery, but it scratches my itch and so I treat is as fun leisure time.</p> <h2 id="editing-experience">Editing experience</h2> <p>The reason I mentioned in my <a href="proxy.php?url=/blog/migrating-from-a-static-site-to-ghost/">previous post</a> of switching to Ghost that the main reason was ease of publication.</p> <blockquote> <p>Three words. Ease of publication.</p> </blockquote> <p>Well, I guess that didn&#8217;t last long. I didn&#8217;t find Ghost to be easy to publish. Because the way I write is that I keep adding to a post piecemeal. Slowly over time I build up a post, and the task of opening up the ghost admin panel and finding the correct place to add my thoughts was frustrating enough to not write them at all.</p> <p>Since then I&#8217;ve founded obsidian and have really found a working flow with it.</p> <p>Authoring and storing notes in obsidian is fast. And so I&#8217;ve switched back to my trusty old static site generator called Genox: <a href="proxy.php?url=https://github.com/oxalorg/genox">https://github.com/oxalorg/genox</a> </p> <p>It&#8217;s the only one which I like and I encourage you to fork it!</p> <p><img src="proxy.php?url=/media/back-to-genox-1.png" alt="" /></p> 2022-08-02T00:00:00+00:00 https://oxal.org/blog/clojurians-log-v2-update-2/ Clojurians Log v2 - Update #2 2022-02-15T00:00:00+00:00 oxalorg <p>Hello fellow clojurians!</p> <p>The beta release of Clojurians log v2 is live at: <a href="proxy.php?url=https://clojurians-log-v2.oxal.org/">https://clojurians-log-v2.oxal.org/</a> ๐Ÿฆ„ ๐Ÿ˜Š</p> <p>Source code: github.com/oxalorg/clojurians-log-v2</p> <p>The initial goal we had with clojurians=log-v2 was mostly around improved full text searching. But as we all know the clojurians slack was sponsored for a paid version for the next 1 year (thanks to all who got this done!) which means our direction has to change a little.</p> <p>Although full text search will be available and working on the v2, we would like to focus more on other parts of the app now and find ways to make the logs more usefull because paid slack already does search very well.</p> <p>With that said the beta release now had the following work put into it:</p> <ul> <li>The importing of data for threaded messages / replies / reactions has been added. This was huge as this was the last key pending in the idempotent imports ๐ŸŽ‰.</li> <li>UI now shows all thread messages directly under the parent. This is effecient enough to not cause any slowdowns.</li> <li>The UI is now fully mobile responsive ๐Ÿ“ฑ. That means easy reading of logs from your bed, and also better SEO points by Google!</li> <li>That brings us to SEO. Not only are we fully compatible with the existing clojurians-log, but we have also added lots of open-graph tags, html structure and other small changes for optimal SEO brownie points. The improved seo tags should also now help &#8220;Clojure&#8221; to rank higher in the TIOBE index.</li> <li>Lots of behind the scene work to improve deployment strategy. Although not fully automated, I have some local scripts which gets me to deploy new versions quite easily. A future task would be to convert it into GH actions.</li> <li>Added support for db migrations using migratus.</li> <li>Analytics for the website are now PUBLIC. You can view them at <a href="proxy.php?url=https://clojurians-log-v2.goatcounter.com/">https://clojurians-log-v2.goatcounter.com/</a></li> </ul> <p>I would like to extend immense gratitude to the clojurists-together team and the clojure community for helping us get here.</p> <p>So what&#8217;s next? We do have a near-future roadmap of things which should land in very soon:</p> <ul> <li>Right now all the data imports are manual. Implementing of real-time socket API and making it compatible / interchangable with the idempotent incremental imports (w/ option to backfill data) is posing a bit of a hassle which needs some more work</li> <li>Although we&#8217;ve got the reactions into the database, rendering of reactions as emojis on the UI is pending</li> <li>Contacting a clojurians slack admin to get the data up-to-date</li> </ul> <p>After that, there are tons more ideas listed on the repo which should keep us busy throughout the next year! Super humbled and excited to continue working on this ๐Ÿ™๐Ÿ˜Š</p> <p>Cheers and Happy New Years everyone ๐ŸŒŸ๐ŸŽ„</p> <p>Mitesh (@oxalorg)</p> 2022-02-15T00:00:00+00:00 https://oxal.org/blog/migrating-from-a-static-site-to-ghost/ Migrating from a static site to Ghost ๐Ÿ‘ป 2021-12-29T00:00:00+00:00 oxalorg <p>Until now I was using my own SSG (static site generator) called <a href="proxy.php?url=https://github.com/oxalorg/genox">genox</a> to deploy <a href="proxy.php?url=https://oxal.org/">oxal.org</a>. Which I absolutely adore. Even now. It&#8217;s one of things I&#8217;m super proud of (especially it&#8217;s name ๐Ÿคช, and I still use it for many other websites.</p> <p>But still, <strong>STILL</strong> I ported my blog to use ghost.org</p> <h2 id="why">Why?</h2> <p>Three words. Ease of publication.</p> <p>I found that writing posts inside of Emacs makes me lazy. It doesn&#8217;t feel like writing. It feels like I am coding.</p> <p>I don&#8217;t know how to explain it. But I just never ended up writing posts locally on my machine. Maybe I won&#8217;t do it now either, but this was a big enough bottleneck that it&#8217;s worth optimizing for. What use is a static blog without content?</p> <p>Ghost gives me a whole bunch of things which makes me <strong>EXCITED</strong>. Beautiful editor. Ability to make edits quickly. Copy paste things. Upload images easily. Paste images from clipboard. Auto image compression. No need for manual git commits ๐Ÿ™ˆ. And so much more.</p> <p>So if I think that my giddy excitement over ghost&#8217;s proper web interface will allow me to make 1 extra post per month, this is a net win for me.</p> <h2 id="technical-details">Technical details</h2> <p>My previous site was a static site. Now it&#8217;s not ๐Ÿคฎ . But wait. I already host a lot of services on my Hetzner VPS. So just one more right? And honestly, hosting Ghost using docker is dead simple.</p> <h3 id="markdown-to-mobiledoc">Markdown to mobiledoc</h3> <p>In genox, all my posts were in a nested directory full of markdown files. I needed to convert them into a <code>json</code> file with <code>mobiledoc</code> fields.</p> <p>But mobiledoc support is pathetic. Finding direct markdown to mobiledoc was taking me more than 5 minutes so I instead converted markdown to html first (which is already being done by <code>genox</code>) and then convert <code>html</code> to <code>mobiledoc</code> using Ghost&#8217;s migrate tool.</p> <p>Adding a <code>genox -&gt; json</code> export was really easy. It literally took me like 5 minutes, thanks to genox ๐Ÿ”ฅ</p> <p>This outputs a single json file which is in a format acceptable by ghost.</p> <p>I then followed the <a href="proxy.php?url=https://ghost.org/docs/migration/content/">docs here</a> to covert the above json with <code>html</code> fields to <code>mobiledoc</code> fields. After that I imported it from the ghost dashboard, and Voila!</p> <h3 id="ghost-docker">Ghost ๐Ÿ‘ป + Docker ๐Ÿณ = ๐Ÿ’™</h3> <p>Ghost has an official docker image which stores all data in a volume, including the database which is stored in a single sqlite file.</p> <p>I ran the ghost instance using docker with a custom mounted volume which is tracked by git (including the sqlite file):</p> <p>For production I use portainer on my Hetzner server. But for now I&#8217;m just running a simple docker command using my in-built ark utility which controlls my server.</p> <h3 id="ghost-theme">Ghost theme</h3> <p>Ofcourse I like my existing design too much, so I used a base theme and used <a href="proxy.php?url=https://github.com/oxalorg/sakura">sakura</a> to override most parts of it.</p> <h3 id="serving-with-caddy-server">Serving with Caddy server</h3> <p>Some path on my site still needed to be static, but rest of the paths need to be forwarded to ghost. I acommplish that using the following <code>Caddyfile</code>:</p> <h3 id="backup-strategy">Backup strategy</h3> <p>The directory <code>/srv/ox/oxal-ghost</code> is a git repo. I created a GitHub <strong>DEPLOY KEY</strong> with write access.</p> <p>Added a cron job to add, commit, and push all the changes to the repo every 1 hour. This way the <code>data/ghost.db</code> file and all the uploaded <code>images</code> will always be backed up. This way migrating to a new server should just be a <code>git pull</code> away.</p> 2021-12-29T00:00:00+00:00 https://oxal.org/blog/clojurians-log-v2-alpha-release/ Clojurians Log v2 - Alpha Release 2021-11-01T00:00:00+00:00 oxalorg <p>๐Ÿ‘‹ Hello fellow clojurians!</p> <p>It has been around <a href="proxy.php?url=https://oxal.org/blog/clojurians-log-v2-funding/">40 days since</a> I started the work on Clojurians log v2 for this funding cycle and it&#8217;s now time for an alpha release.</p> <p>Without waiting further, the v2 is now live at: <a href="proxy.php?url=https://clojurians-log-v2.oxal.org/">https://clojurians-log-v2.oxal.org/</a> ๐Ÿฆ„ ๐Ÿ˜Š</p> <p>Source code: <a href="proxy.php?url=https://github.com/oxalorg/clojurians-log-v2">github.com/oxalorg/clojurians-log-v2</a></p> <p>PS: it&#8217;s not 100% usable on mobile yet, please use a desktop for the time being!</p> <p>Features added in this release are:</p> <ul> <li>Search across the entire slack logs <ul> <li>try it here: <a href="proxy.php?url=https://clojurians-log-v2.oxal.org/search?q=hello+clojurians">https://clojurians-log-v2.oxal.org/search?q=hello+clojurians</a></li> <li>lots of work is still pending to get this into an actual usable state, but hey it works! ๐Ÿ’ƒ</li> <li>searches over 2 million messages in ~50-500ms!</li> </ul></li> <li>ansible scripts to setup the server <ul> <li>setup user, fail2ban, firewall, postgres, docker, java, clojure, git, caddy, SSL, etc.</li> </ul></li> <li>๐Ÿฟ Web app deployed live on a domain ๐ŸŒ on a Hetzner VPS (2cpu, 4gb ram)</li> <li>setup docker deployment and recreating process</li> <li>setup the production postgres db</li> <li>idempotent import data for channels, members, and messages <ul> <li>it works (atleast for the current schema, most likely to change soon)</li> <li>fixed mysterious null character issues in slack dumps, postgres really doesn&#8217;t like to eat <code>\u0000</code> chars for breakfast</li> <li>only partial data is imported (~2 million messages) as I only have slack dumps till June 2021</li> <li>expect things to break because I&#8217;m still messing around with indexes, rankings, and migrations to optimise search and other queries ๐Ÿคช</li> <li>imports for reactions, threads, etc are still pending</li> </ul></li> <li>added <code>mrkdwn</code> message rendering with emojis</li> <li>more advanced configuration, dynamic based on <code>:dev</code> vs <code>:prod</code></li> <li>proper support for secrets via a <code>secrets.edn</code> file</li> <li>lots of small bug fixes, UI/UX changes, optimized db queries, refactoring, etc.</li> </ul> <p>I wanted to ship the minimal possible version first and then iterate over it. And I&#8217;m quite happy with this. Looking forward to adding lots more features and polish over the next few months, especially related to text searching!</p> <p>Please feel free to hop over in #clojureverse-ops channel in clojurians slack to discuss anything related to this project! Thanks.</p> 2021-11-01T00:00:00+00:00