-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathindex.html
More file actions
14 lines (14 loc) · 18.1 KB
/
index.html
File metadata and controls
14 lines (14 loc) · 18.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!doctype html><html lang=en dir=auto><head><meta name=generator content="Hugo 0.155.2"><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name=robots content="index, follow"><title>Bart de Goede</title><meta name=keywords content="tech,programming,python,elasticsearch,search,ssl"><meta name=description content="Random ramblings about programming, tech, and anything else really."><meta name=author content="Bart de Goede"><link rel=canonical href=https://bart.degoe.de/><link crossorigin=anonymous href=/assets/css/stylesheet.36f047f2ce56ab2fb14ab95d8e950744d73f8386b98a3e0a6da7886691f40177.css integrity="sha256-NvBH8s5Wqy+xSrldjpUHRNc/g4a5ij4KbaeIZpH0AXc=" rel="preload stylesheet" as=style><link rel=icon href=https://bart.degoe.de/favicon.ico><link rel=icon type=image/png sizes=16x16 href=https://bart.degoe.de/favicon-16x16.png><link rel=icon type=image/png sizes=32x32 href=https://bart.degoe.de/favicon-32x32.png><link rel=apple-touch-icon href=https://bart.degoe.de/apple-touch-icon.png><link rel=mask-icon href=https://bart.degoe.de/safari-pinned-tab.svg><meta name=theme-color content="#2e2e33"><meta name=msapplication-TileColor content="#2e2e33"><link rel=alternate type=application/rss+xml href=https://bart.degoe.de/index.xml title=rss><link rel=alternate type=application/json href=https://bart.degoe.de/index.json title=json><link rel=alternate hreflang=en href=https://bart.degoe.de/><noscript><style>#theme-toggle,.top-link{display:none}</style><style>@media(prefers-color-scheme:dark){:root{--theme:rgb(29, 30, 32);--entry:rgb(46, 46, 51);--primary:rgb(218, 218, 219);--secondary:rgb(155, 156, 157);--tertiary:rgb(65, 66, 68);--content:rgb(196, 196, 197);--code-block-bg:rgb(46, 46, 51);--code-bg:rgb(55, 56, 62);--border:rgb(51, 51, 51)}.list{background:var(--theme)}.list:not(.dark)::-webkit-scrollbar-track{background:0 0}.list:not(.dark)::-webkit-scrollbar-thumb{border-color:var(--theme)}}</style></noscript><script>(localStorage.getItem("pref-theme")==="dark"||localStorage.getItem("pref-theme")!=="light"&&window.matchMedia("(prefers-color-scheme: dark)").matches)&&document.documentElement.classList.add("dark")</script><link rel=preconnect href=https://cdn.jsdelivr.net crossorigin><link rel=preconnect href=https://www.googletagmanager.com crossorigin><link rel=search href=/opensearch.xml type=application/opensearchdescription+xml title="Search bart.degoe.de"><script async src="https://www.googletagmanager.com/gtag/js?id=G-6JBRP5YVDB"></script><script>var doNotTrack=!1,dnt=navigator.doNotTrack||window.doNotTrack||navigator.msDoNotTrack,doNotTrack=dnt=="1"||dnt=="yes";if(!doNotTrack){window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag("js",new Date),gtag("config","G-6JBRP5YVDB")}</script><meta property="og:url" content="https://bart.degoe.de/"><meta property="og:site_name" content="Bart de Goede"><meta property="og:title" content="Bart de Goede"><meta property="og:description" content="Random ramblings about programming, tech, and anything else really."><meta property="og:locale" content="en-us"><meta property="og:type" content="website"><meta property="og:image" content="https://bart.degoe.de/img/default-cover.jpg"><meta name=twitter:card content="summary_large_image"><meta name=twitter:image content="https://bart.degoe.de/img/default-cover.jpg"><meta name=twitter:title content="Bart de Goede"><meta name=twitter:description content="Random ramblings about programming, tech, and anything else really."><script type=application/ld+json>{"@context":"https://schema.org","@type":"Organization","name":"Bart de Goede","url":"https://bart.degoe.de/","description":"Random ramblings about programming, tech, and anything else really.","logo":"https://bart.degoe.de/favicon.ico","sameAs":["https://github.com/bartdegoede","https://www.linkedin.com/in/bart-de-goede/","https://twitter.com/bartdegoede","/index.xml"]}</script></head><body class=list id=top><script>localStorage.getItem("pref-theme")==="dark"?document.body.classList.add("dark"):localStorage.getItem("pref-theme")==="light"?document.body.classList.remove("dark"):window.matchMedia("(prefers-color-scheme: dark)").matches&&document.body.classList.add("dark")</script><header class=header><nav class=nav><div class=logo><a href=https://bart.degoe.de/ accesskey=h title="Bart de Goede (Alt + H)">Bart de Goede</a><div class=logo-switches><button id=theme-toggle accesskey=t title="(Alt + T)" aria-label="Toggle theme">
<svg id="moon" width="24" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></svg>
<svg id="sun" width="24" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg></button></div></div><ul id=menu><li><a href=https://bart.degoe.de/about/ title=About><span>About</span></a></li></ul></nav></header><main class=main><div class=search-container style=margin-bottom:2rem><input id=searchInput type=search placeholder="Search blog posts..." autocomplete=off style="width:100%;padding:.75rem 1rem;font-size:1rem;border:1px solid var(--border);border-radius:8px;background:var(--entry);color:var(--primary)"><div id=searchResults style=margin-top:1rem></div></div><div id=allPosts><article class=post-entry><figure class=entry-cover><img loading=lazy src=https://bart.degoe.de/img/2026-02-19-ai-agent-dungeon-crawlerverse-sdk/cover.png alt></figure><header class=entry-header><h2 class=entry-hint-parent>Your OpenClaw can book flights. But can it survive a dungeon crawl?</h2></header><div class=entry-content><p>Everyone's building AI agents that send emails and book flights. How about one that fights monsters in a dungeon? Here's how you can too.</p></div><footer class=entry-footer><span title='2026-02-19 12:00:00 -0700 -0700'>February 19, 2026</span> · <span>10 min</span> · <span>Bart de Goede</span></footer><a class=entry-link aria-label="post link to Your OpenClaw can book flights. But can it survive a dungeon crawl?" href=/ai-agent-dungeon-crawl/></a></article><article class=post-entry><figure class=entry-cover><img loading=lazy src=https://bart.degoe.de/img/2026-02-09-semantic-search-python/cover.jpg alt></figure><header class=entry-header><h2 class=entry-hint-parent>Building a semantic search engine in ±250 lines of Python</h2></header><div class=entry-content><p>Our keyword search engine can't find 'alcoholic beverage disaster in England' even though the London Beer Flood is right there. In this post, we add semantic search using sentence-transformers embeddings and cosine similarity to find documents by meaning, not just matching words.</p></div><footer class=entry-footer><span title='2026-02-09 18:00:00 -0700 -0700'>February 9, 2026</span> · <span>14 min</span> · <span>Bart de Goede</span></footer><a class=entry-link aria-label="post link to Building a semantic search engine in ±250 lines of Python" href=/building-a-semantic-search-engine-in-250-lines-of-python/></a></article><article class=post-entry><header class=entry-header><h2 class=entry-hint-parent>Modernizing my 150-line Python search engine: Yahoo! dumps -> Hugging Face 🤗</h2></header><div class=entry-content><p>A few years ago I wrote a full-text search engine in 150 lines of Python. The Wikipedia data source it relied on has since been discontinued, and the tooling around it was showing its age. I wanted to (finally) write a follow-up about semantic search, but I realized that I had to get the old repository in a working state first. It's now using Hugging Face (🤗) datasets, uv, ruff, pytest, and GitHub Actions, without touching the core search logic.</p></div><footer class=entry-footer><span title='2026-02-09 12:00:00 -0700 -0700'>February 9, 2026</span> · <span>6 min</span> · <span>Bart de Goede</span></footer><a class=entry-link aria-label="post link to Modernizing my 150-line Python search engine: Yahoo! dumps -> Hugging Face 🤗" href=/modernizing-python-search-engine/></a></article><article class=post-entry><header class=entry-header><h2 class=entry-hint-parent>Migrating my hopelessly outdated Hugo blog with Claude Code</h2></header><div class=entry-content><p>I haven't really touched my blog since 2019. The theme was ancient, jQuery was everywhere, and I kept putting off the inevitable migration. Then I decided to let an LLM do it. Here's what happened when Claude Code spent an evening trying to modernize my setup.</p></div><footer class=entry-footer><span title='2025-10-26 02:00:00 -0700 -0700'>October 26, 2025</span> · <span>7 min</span> · <span>Bart de Goede</span></footer><a class=entry-link aria-label="post link to Migrating my hopelessly outdated Hugo blog with Claude Code" href=/migrating-hugo-blog-with-claude-code/></a></article><article class=post-entry><figure class=entry-cover><img loading=lazy src=https://bart.degoe.de/img/2021-03-24-building-a-full-text-search-engine-150-lines-of-code/cover.jpg alt></figure><header class=entry-header><h2 class=entry-hint-parent>Building a full-text search engine in 150 lines of Python code</h2></header><div class=entry-content><p>Full-text search is everywhere. From finding a book on Scribd, a movie on Netflix, toilet paper on Amazon, or anything else on the web through Google (like [how to do your job as a software engineer](https://localghost.dev/2019/09/everything-i-googled-in-a-week-as-a-professional-software-engineer/)), you've searched vast amounts of unstructured data multiple times today. What's even more amazing, is that you've even though you searched millions (or [billions](https://www.worldwidewebsize.com/)) of records, you got a response in milliseconds. In this post, we are going to build a basic full-text search engine that can search across millions of documents and rank them according to their relevance to the query in milliseconds, in less than 150 lines of code!</p></div><footer class=entry-footer><span title='2021-03-24 20:00:12 -0700 -0700'>March 24, 2021</span> · <span>15 min</span> · <span>Bart de Goede</span></footer><a class=entry-link aria-label="post link to Building a full-text search engine in 150 lines of Python code" href=/building-a-full-text-search-engine-150-lines-of-code/></a></article><article class=post-entry><header class=entry-header><h2 class=entry-hint-parent>Use Google Cloud Text-to-Speech to create an audio version of your blog posts</h2></header><div class=entry-content><p>Audio is big. Like really big, and growing fast, to the tune of "two-thirds of the population listens to online audio" and "weekly online listeners reporting an average nearly 17 hours of listening in the last week". These numbers include all kinds of audio, from online radio stations, audiobooks, streaming services and podcasts (hi Spotify!). It makes sense too. Consuming audio content is easier to consume and more engaging than written content while you're on the go, exercising, commuting or doing household chores. But what do you do if you're like me and don't have the time or recording equipment to ride this podcasting wave, and just write the occasional blog post?</p></div><footer class=entry-footer><span title='2019-10-29 08:00:00 -0700 -0700'>October 29, 2019</span> · <span>10 min</span> · <span>Bart de Goede</span></footer><a class=entry-link aria-label="post link to Use Google Cloud Text-to-Speech to create an audio version of your blog posts" href=/use-google-cloud-text-to-speech-to-create-an-audio-version-of-your-blog-posts/></a></article><article class=post-entry><header class=entry-header><h2 class=entry-hint-parent>Use Hugo Output Formats to generate Lunr index files for your static site search</h2></header><div class=entry-content><p>I've been using Lunr.js to enable some basic site search on this blog. Lunr.js requires an index file that contains all the content you want to make available for search. In order to generate that file, I had a kind of hacky setup, depending on running a Grunt script on every deploy, which introduces a dependency on node, and nobody really wants any of that for just a static HTML website.</p></div><footer class=entry-footer><span title='2019-07-12 15:27:40 -0700 -0700'>July 12, 2019</span> · <span>3 min</span> · <span>Bart de Goede</span></footer><a class=entry-link aria-label="post link to Use Hugo Output Formats to generate Lunr index files for your static site search" href=/use-hugo-output-formats-to-generate-lunr-index-files/></a></article><article class=post-entry><header class=entry-header><h2 class=entry-hint-parent>Custom OpenSearch: search from your URL bar</h2></header><div class=entry-content><p></p></div><footer class=entry-footer><span title='2018-11-21 11:00:00 -0800 -0800'>November 21, 2018</span> · <span>5 min</span> · <span>Bart de Goede</span></footer><a class=entry-link aria-label="post link to Custom OpenSearch: search from your URL bar" href=/tab-plus-search-from-your-url-bar-with-opensearch/></a></article><article class=post-entry><header class=entry-header><h2 class=entry-hint-parent>Free SSL on Github Pages with a custom domain: Part 2 - Let's Encrypt</h2></header><div class=entry-content><p></p></div><footer class=entry-footer><span title='2018-05-02 22:34:06 +0200 +0200'>May 2, 2018</span> · <span>4 min</span> · <span>Bart de Goede</span></footer><a class=entry-link aria-label="post link to Free SSL on Github Pages with a custom domain: Part 2 - Let's Encrypt" href=/github-pages-and-lets-encrypt/></a></article><article class=post-entry><header class=entry-header><h2 class=entry-hint-parent>Free SSL with a custom domain on GitHub Pages</h2></header><div class=entry-content><p></p></div><footer class=entry-footer><span title='2018-03-28 23:55:40 +0200 +0200'>March 28, 2018</span> · <span>6 min</span> · <span>Bart de Goede</span></footer><a class=entry-link aria-label="post link to Free SSL with a custom domain on GitHub Pages" href=/free-ssl-on-github-pages-with-a-custom-domain/></a></article></div><footer class=page-footer><nav class=pagination><a class=next href=https://bart.degoe.de/page/2/><span class=button__text>Older</span>
<span class=button__icon>→</span></a></nav></footer></main><footer class=footer><span>© 2026 <a href=https://bart.degoe.de/>Bart de Goede</a></span></footer><a href=#top aria-label="go to top" title="Go to Top (Alt + G)" class=top-link id=top-link accesskey=g><svg viewBox="0 0 12 6" fill="currentColor"><path d="M12 6H0l6-6z"/></svg>
</a><script src=https://cdn.jsdelivr.net/npm/fuse.js@7.1.0></script><script>document.addEventListener("DOMContentLoaded",function(){const t=document.getElementById("searchInput"),e=document.getElementById("searchResults"),n=document.getElementById("allPosts");if(!t)return;let s=null,o=null;fetch("/index.json").then(e=>e.json()).then(e=>{o=e;const t={isCaseSensitive:!1,shouldSort:!0,location:0,distance:1e3,threshold:.4,minMatchCharLength:2,keys:[{name:"title",weight:.8},{name:"content",weight:.5},{name:"categories",weight:.3}]};s=new Fuse(e,t)}).catch(e=>console.error("Error loading search index:",e)),t.addEventListener("input",function(t){const i=t.target.value.trim();if(i.length<2){e.innerHTML="",e.style.display="none",n.style.display="block";return}if(!s){e.innerHTML='<p style="color: var(--secondary);">Loading search index...</p>',e.style.display="block";return}const o=s.search(i);if(n.style.display="none",e.style.display="block",o.length===0){e.innerHTML='<p style="color: var(--secondary); padding: 1rem 0;">No results found for "'+i+'"</p>';return}let a='<div style="margin-bottom: 1.5rem; color: var(--secondary); font-size: 0.95rem;">Found '+o.length+" result"+(o.length===1?"":"s")+"</div>";o.slice(0,10).forEach(e=>{const t=e.item,n=t.content?t.content.substring(0,150).replace(/\n/g," ").trim()+"...":"";a+=`
<article class="post-entry">
<header class="entry-header">
<h2 class="entry-hint-parent">${t.title}</h2>
</header>
${n?`<div class="entry-content"><p>${n}</p></div>`:""}
${t.categories&&t.categories.length>0?`<footer class="entry-footer">${t.categories.join(", ")}</footer>`:""}
<a class="entry-link" aria-label="post link to ${t.title}" href="${t.href}"></a>
</article>
`}),e.innerHTML=a}),t.addEventListener("keydown",function(s){s.key==="Escape"&&(t.value="",e.innerHTML="",e.style.display="none",n.style.display="block")})})</script><script>let menu=document.getElementById("menu");menu&&(menu.scrollLeft=localStorage.getItem("menu-scroll-position"),menu.onscroll=function(){localStorage.setItem("menu-scroll-position",menu.scrollLeft)}),document.querySelectorAll('a[href^="#"]').forEach(e=>{e.addEventListener("click",function(e){e.preventDefault();var t=this.getAttribute("href").substr(1);window.matchMedia("(prefers-reduced-motion: reduce)").matches?document.querySelector(`[id='${decodeURIComponent(t)}']`).scrollIntoView():document.querySelector(`[id='${decodeURIComponent(t)}']`).scrollIntoView({behavior:"smooth"}),t==="top"?history.replaceState(null,null," "):history.pushState(null,null,`#${t}`)})})</script><script>var mybutton=document.getElementById("top-link");window.onscroll=function(){document.body.scrollTop>800||document.documentElement.scrollTop>800?(mybutton.style.visibility="visible",mybutton.style.opacity="1"):(mybutton.style.visibility="hidden",mybutton.style.opacity="0")}</script><script>document.getElementById("theme-toggle").addEventListener("click",()=>{document.body.className.includes("dark")?(document.body.classList.remove("dark"),localStorage.setItem("pref-theme","light")):(document.body.classList.add("dark"),localStorage.setItem("pref-theme","dark"))})</script></body></html>