{"author":{"name":"Rasmus Grouleff","uri":"https:\/\/mstdn.social\/@nerdd.dk","email":"rasmus@nerdd.dk"},"id":"https:\/\/nerdd.dk\/feed.atom.xml","link":[{"@attributes":{"href":"https:\/\/nerdd.dk\/feed.atom.xml","rel":"self","type":"application\/atom+xml"}},{"@attributes":{"href":"https:\/\/nerdd.dk\/","rel":"alternate","type":"text\/html","hreflang":"en"}}],"subtitle":"This is the tiny corner of the Internet belonging to Rasmus Grouleff, a freelance software developer.","title":"Nerdd.dk","updated":"2026-03-23T11:11:13+00:00","entry":[{"author":{"name":"Rasmus Grouleff","uri":"https:\/\/mstdn.social\/@nerdd.dk","email":"rasmus@nerdd.dk"},"category":[{"@attributes":{"term":"ai","label":"tag"}},{"@attributes":{"term":"rant","label":"tag"}},{"@attributes":{"term":"politics","label":"tag"}},{"@attributes":{"term":"open-source","label":"tag"}},{"@attributes":{"term":"general-election","label":"tag"}}],"content":"<p>My hate for AI in the form that is being peddled by techno-fascist dude-bros right now burns with the intensity of a thousand suns, and when it is used for political purposes, I hate it even more. So I created <a href=\"https:\/\/slopaganda.dk\">Slopaganda.dk<\/a>.<\/p>\n\n<p>So when DR (the Danish public service broadcasting corporation) wrote an <a href=\"https:\/\/www.dr.dk\/nyheder\/politik\/folketingsvalg\/partiernes-ai-videoer-vaekker-opsigt-valgkampen-praeges-af-slopaganda\">article about the use of AI-slop in the election campaigns<\/a> leading up to the Danish general election on March 24, I <a href=\"https:\/\/mstdn.social\/@nerdd\/116147541285282604\">posted on Mastodon<\/a>, that it would be handy to have a website tracking which candidates was using AI-slop. I even posted, <a href=\"https:\/\/mstdn.social\/@nerdd\/116147713703642240\">I was willing to buy a domain name for it and host it<\/a>, if someone would build it and keep it updated as more slop was found.<\/p>\n\n<p>Someone did come forward and offered to build it, but when they disclosed that they had used an AI chatbot while building it, I immediately declined hosting that.<\/p>\n\n<p>I had however already secured the domain <a href=\"https:\/\/slopaganda.dk\">Slopaganda.dk<\/a>, so at 2:30 AM last Sunday, when I couldn\u2019t sleep because of anger and frustration, I dragged my computer into bed and furiously hacked together the simplest possible site generator that could turn a CSV-file containing submitted tips into an HTML page. The first version was small enough to fit into <a href=\"https:\/\/mstdn.social\/@nerdd\/116160733528465459\">a single Mastodon-post<\/a>.<\/p>\n\n<p>The way it works is incredibly simple. On the page there is an iframe that shows a <a href=\"https:\/\/tally.so\">tally.so<\/a> form where anyone anonymously can submit tips about slopaganda. When there are new submissions, I verify that it actually is slop, download the submissions as a CSV-file, rebuild the page, and upload it to the server.<\/p>\n\n<p>Later that Sunday, the domain name was activated, and the site was live.<\/p>\n\n<p>My original plan had been to not associate myself too much with the site during the election, and then write about it here afterwards, but that plan pretty quickly went down the drain, because I started pushing it quite hard on Mastodon. You didn\u2019t have to be too smart to put two and two together to figure out, I might be the creator.<\/p>\n\n<p>Since the first version, I have added some extra features like an RSS-feed and the ability to apply modifications to submissions, if a name is spelled incorrectly, something has been submitted twice etc. I was also asked if the source code was available online, and if I accepted pull requests. After a couple of minutes I had put <a href=\"https:\/\/codeberg.org\/rb_grouleff\/slopaganda.dk\">the source up on Codeberg.org<\/a> and released it in the public domain.<\/p>\n\n<p>Anyway I am pretty happy with it, and <a href=\"https:\/\/tally.so\">tally.so<\/a> seems like a great service for gathering tips.<\/p>\n\n<p>If you are a Dane or you know someone Danish, I would really like, if you would talk about it to as many as possible. Because this is a community effort. I cannot monitor every media and wander every street looking for slopaganda. And if you see something that might be slop, <a href=\"https:\/\/slopaganda.dk\"><em>please<\/em> submit a tip<\/a>!<\/p>","id":"repo:\/\/posts.collection\/_posts\/2026-03-07-a-general-election-in-the-age-of-ai-slop.md","link":{"@attributes":{"href":"https:\/\/nerdd.dk\/2026\/03\/07\/a-general-election-in-the-age-of-ai-slop\/","rel":"alternate","type":"text\/html","title":"A general election in the age of \"AI\"-slop"}},"published":"2026-03-07T15:46:21+00:00","summary":"AI slop is making its mark on the Danish general election, and I hate it. So I decided to track its usage and put it on display for everyone to see on slopaganda.dk.","title":"A general election in the age of \u201cAI\u201d-slop","updated":"2026-03-07T15:46:21+00:00"},{"author":{"name":"Rasmus Grouleff","uri":"https:\/\/mstdn.social\/@nerdd.dk","email":"rasmus@nerdd.dk"},"category":[{"@attributes":{"term":"programming","label":"tag"}},{"@attributes":{"term":"css","label":"tag"}},{"@attributes":{"term":"rant","label":"tag"}},{"@attributes":{"term":"tailwindcss","label":"tag"}}],"content":"<p>For more than a year now, I have been using Tailwind CSS, both on my own website and for clients. So it is on a reasonably informed basis I can state, I don\u2019t like it.<\/p>\n\n<p>Let me explain.<\/p>\n\n<h2 id=\"endless-lists-of-classes\">Endless lists of classes<\/h2>\n\n<p>Styling pages with utility classes like Tailwind CSS intends leads to adding lots of classes to the <code class=\"highlighter-rouge\">HTML<\/code> elements, and what ends up happening is that it makes it incredibly hard to figure out the structure of a page, when the majority of the content in either my editor or in the browser dev tools consists of classes on elements. This is not made any better by the fact that many of the classes are the same.<\/p>\n\n<h2 id=\"wheres-my-repl\">Where\u2019s my REPL?<\/h2>\n\n<p>When I build pages, I usually use the DOM and style inspector in the browser in a way like I would use a REPL for backend-stuff. I select an element in the inspector, add a bit of CSS or an extra class, look at what it looks like in the browser, and then tweaking the styling, until I\u2019m satisfied and actually implement it.<\/p>\n\n<p>Tailwind CSS breaks this in two ways. The first: the selectors Tailwind CSS generates are not always easy to understand, especially not in plugins (take a look at <a href=\"https:\/\/github.com\/tailwindlabs\/tailwindcss-typography\">tailwind-typography<\/a>). The second: the generated CSS only includes styles for whatever classes I have used before, so if I haven\u2019t used <code class=\"highlighter-rouge\">gap-4<\/code> before, I\u2019m out of luck when playing around in the browser, unless I look up how Tailwind CSS generates the styles for <code class=\"highlighter-rouge\">gap-4<\/code> and paste them into the inspector.<\/p>\n\n<h2 id=\"arbitrary-values\">Arbitrary values<\/h2>\n\n<p>When the default values Tailwind CSS provides are not enough, and you need to apply something specific to a single element, you of course can. But instead of using plain CSS, they seem to encourage using their bracket notation in classes. This ends up with something like <code class=\"highlighter-rouge\">&lt;div class=\"top-[117px]\"&gt;<\/code> or <code class=\"highlighter-rouge\">&lt;div class=\"grid grid-cols-[1fr_500px_2fr]\"&gt;<\/code>. That is pretty much just a bad way of writing <code class=\"highlighter-rouge\">&lt;div style=\"grid-template-columns: 1fr 500px 2fr\"&gt;<\/code>.<\/p>\n\n<h2 id=\"cascade-no-more\">Cascade no more<\/h2>\n\n<p>Tailwind CSS\u2019s design renders the cascading part of CSS almost useless. Tailwind CSS offers several ways of defining more general styles, but the documentation encourages using the utility classes as much as possible, meaning styles are only ever applied to the specific elements with the specific utility classes.<\/p>\n\n<h2 id=\"overly-specific-styles\">Overly specific styles<\/h2>\n\n<p>The discouragement of using the cascade results in overly specific styles that are hard to adjust in a general way. This leads to having to repeatedly adding the same utility classes over and over again. The argument is to instead rely on the templating language used for rendering the HTML and build components in that.<\/p>\n\n<h2 id=\"component-libraries---utility-or-semantic\">Component libraries - utility or semantic?<\/h2>\n\n<p>Component libraries try to solve some of the annoyances above. They minimize the need for utility classes and let you use more semantic classes, but this is the completely opposite of what Tailwind CSS is preaching. Why use Tailwind CSS at all in the first place, if you don\u2019t want to use utility classes? And with component libraries, I always seem to end up with having to write something like <code class=\"highlighter-rouge\">&lt;button class=\"btn\"&gt;Click me&lt;\/button&gt;<\/code>, repeating the fact that it actually <em>is<\/em> a button I am adding.<\/p>\n\n<h2 id=\"extra-non-semantic-elements\">Extra non-semantic elements<\/h2>\n\n<p>It has happened several times that I have had to add extra child divs to get elements to behave or look in a certain way, after I have muddled around with all sorts of funky <a href=\"#arbitrary-values\">arbitrary values<\/a> or <a href=\"#endless-lists-of-classes\">endless lists of classes<\/a>. Sometimes I have even needed extra divs, arbitrary values, and endless lists of classes as a nice triple whammy.<\/p>\n\n<h2 id=\"you-do-you\">You do you<\/h2>\n\n<p>This is not to dunk on Tailwind CSS at all (well, it kinda is, I guess\u2026). Even though it isn\u2019t for me, it is not something that would make me turn down clients, if that is what they want to use. But if I am asked, I will not recommend it, and I will not use it on my own things.<\/p>","id":"repo:\/\/posts.collection\/_posts\/2026-01-25-i-don-t-like-tailwind-css.md","link":{"@attributes":{"href":"https:\/\/nerdd.dk\/2026\/01\/25\/i-don-t-like-tailwind-css\/","rel":"alternate","type":"text\/html","title":"I don't like Tailwind CSS"}},"published":"2026-01-25T14:53:25+00:00","summary":"After having used Tailwind CSS for a year, I have come to realize I really don't like it. Let me tell you why.","title":"I don\u2019t like Tailwind CSS","updated":"2026-01-25T14:53:25+00:00"},{"author":{"name":"Rasmus Grouleff","uri":"https:\/\/mstdn.social\/@nerdd.dk","email":"rasmus@nerdd.dk"},"category":[{"@attributes":{"term":"swift","label":"tag"}},{"@attributes":{"term":"macos","label":"tag"}},{"@attributes":{"term":"business","label":"tag"}},{"@attributes":{"term":"software","label":"tag"}},{"@attributes":{"term":"time-tracker","label":"tag"}}],"content":"<p>When the app I have used for years for tracking working hours stopped working a while back, I started looking for a replacement. But I did not find any that satisfied my needs.<\/p>\n\n<p>So I ended up <a href=\"https:\/\/github.com\/rbgrouleff\/Time-Tracker.app\">building my own<\/a>. I have no idea if it could be useful for anybody else, but that certainly won\u2019t stop me from offering it to the world.<\/p>\n\n<h2 id=\"the-current-app-landscape\">The current app landscape<\/h2>\n\n<p>Pretty much all business related apps these days are basically just frontends for subscription services. They usually offer the most basic functionality for free, but if you need anything else, you have to subscribe to yet another service.<\/p>\n\n<p>This is fine, I guess. We all have to make a living in this late stage capitalistic hellscape of a society, we\u2019re living in.<\/p>\n\n<p>The problem I have (besides having to pay for yet another subscription) is, that what happens to my data? Is it secure? What happens, if the company running the service shuts it down? Or if the company is acquired?<\/p>\n\n<p>Why can\u2019t I just pay once for an app, I can run locally, that does not send my data somewhere else, that functions regardless of internet connectivity, and that doesn\u2019t require a third party service to be running?<\/p>\n\n<h2 id=\"the-app\">The app<\/h2>\n\n<p>The app I have built is nothing fancy, but it covers my needs, and it doesn\u2019t come with a subscription or requires a third party service.<\/p>\n\n<p>It is a native macOS app, and it lets me have multiple clients with multiple projects, and it can track the time I spend on each of these projects and how much time I have invoiced on each project.<\/p>\n\n<p><img src=\"\/images\/posts\/scratching-my-own-itch\/app-screenshot.png\" alt=\"Screenshot of the app, there is a client, &quot;MegaCorp&quot;, and it has one project, &quot;The Big One&quot;. The project has three timing sessions with one currently running.\" class=\"mx-auto\" \/><\/p>\n\n<h2 id=\"development\">Development<\/h2>\n\n<p>It is the first time I have built anything native for any Apple device, and it is the first time I have used Swift. So there is probably lots of room for improvement.<\/p>\n\n<p>A lot of the time I felt like the dog in the meme, where it is sat in front of a computer, but somehow I managed to cobble together something that seemingly works (on my computer\u2122\ufe0f).<\/p>\n\n<p><img src=\"\/images\/posts\/scratching-my-own-itch\/i-have-no-idea-what-im-doing.jpg\" alt=\"A golden retriever wearing a red tie and with its tongue out is sitting in front of a laptop with its front paws on the keyboard. It looks slightly confused. In the top left corner there is a caption: &quot;I have no idea what I'm doing&quot;\" class=\"mx-auto\" \/><\/p>\n\n<p>It is all made by me without any use of AI, and the source code and a build of the app is available in the link above. The source is MIT licensed, so do with it, what you will \u2013 within the limits of the license.<\/p>\n\n<p>As the README states, I welcome any issues, suggestions and pull requests. Unless AI has been used to create them.<\/p>\n\n<h2 id=\"say-hi\">Say hi!<\/h2>\n\n<p>I would love to hear from you, if Time Tracker is useful to you. You are also more than welcome to say hi, if you want to talk about the source. I\u2019m sure there is a lot to talk about, since, as I mentioned, it is the first time I have played with Swift and macOS development.<\/p>\n\n<p>\ud83d\udc4b<\/p>","id":"repo:\/\/posts.collection\/_posts\/2025-06-11-scratching-my-own-itch.md","link":{"@attributes":{"href":"https:\/\/nerdd.dk\/2025\/06\/11\/scratching-my-own-itch\/","rel":"alternate","type":"text\/html","title":"Scratching my own itch"}},"published":"2025-06-11T15:02:54+00:00","summary":"I have built a native macOS app for tracking my working hours, because I couldn't find any that didn't require a third party service or a subscription.","title":"Scratching my own itch","updated":"2025-06-11T15:02:54+00:00"},{"author":{"name":"Rasmus Grouleff","uri":"https:\/\/mstdn.social\/@nerdd.dk","email":"rasmus@nerdd.dk"},"category":[{"@attributes":{"term":"ruby","label":"tag"}},{"@attributes":{"term":"bridgetown","label":"tag"}}],"content":"<h2 id=\"introduction\">Introduction<\/h2>\n\n<p>If I want to give myself a better chance to write and publish posts here, it has\nto be as easy as possible.<\/p>\n\n<p>While Bridgetown does make it pretty easy, I think it could be even easier with\na couple of extra commands to manage posts.<\/p>\n\n<p>Let me show you, how I implemented commands for generating new posts and\nupdating both unpublished and published posts.<\/p>\n\n<p>TLDR: You can get <a href=\"https:\/\/gist.github.com\/rbgrouleff\/67a159c489a15976248a7edb14a0c697\">the source code here<\/a><\/p>\n\n<h2 id=\"what-i-want-to-achieve\">What I want to achieve<\/h2>\n\n<p>When I want to write a new post I want to be able to run the command\n<code class=\"highlighter-rouge\">bin\/bridgetown posts new \"Adding a couple of commands for managing posts in Bridgetown\"<\/code>\nand have that create a new markdown file with the right filename and correctly\nformatted frontmatter.<\/p>\n\n<p>If I need to update a post, I want to be able to run the command\n<code class=\"highlighter-rouge\">bin\/bridgetown posts touch 2025-01-16-adding-a-couple-of-commands-for-managing-posts-in-bridgetown<\/code>\nand let it either rename the file with the current date and update the\nfrontmatter if the post is unpublished or only update the frontmatter, if the\npost is published.<\/p>\n\n<h2 id=\"how-on-earth-do-i-add-commands-to-a-bridgetown-project\">How on earth do I add commands to a Bridgetown project?<\/h2>\n\n<p>In a Bridgetown project directory there is a <code class=\"highlighter-rouge\">Rakefile<\/code> where you can add new\ntasks, and they can be invoked using <code class=\"highlighter-rouge\">bin\/bridgetown<\/code>. This is the easiest way\nto add extra commands. It is just not as nice to work with commandline options\nand flags in Rake.<\/p>\n\n<p>Fortunately Bridgetown uses <a href=\"http:\/\/whatisthor.com\">Thor<\/a> for its own commands. Thor comes with\na lot of functionality for handling options and flags, and it can generate help\ntexts as well.<\/p>\n\n<p>The only drawback with Thor is you have to add a <code class=\"highlighter-rouge\">config\/boot.rb<\/code> in the project\ndirectory where you set up Bundler and require the file containing the code for\nyour commands:<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># config\/boot.rb<\/span>\n\n<span class=\"vg\">$LOAD_PATH<\/span> <span class=\"o\">&lt;&lt;<\/span> <span class=\"no\">File<\/span><span class=\"p\">.<\/span><span class=\"nf\">join<\/span><span class=\"p\">(<\/span><span class=\"n\">__dir__<\/span><span class=\"p\">,<\/span> <span class=\"s1\">'..\/lib'<\/span><span class=\"p\">)<\/span> <span class=\"c1\"># lib contains the new command<\/span>\n<span class=\"no\">Bundler<\/span><span class=\"p\">.<\/span><span class=\"nf\">setup<\/span><span class=\"p\">(<\/span><span class=\"ss\">:default<\/span><span class=\"p\">,<\/span> <span class=\"no\">Bridgetown<\/span><span class=\"p\">.<\/span><span class=\"nf\">env<\/span><span class=\"p\">)<\/span>\n<span class=\"nb\">require<\/span> <span class=\"s1\">'local\/commands\/posts'<\/span> <span class=\"c1\"># Requiring the new command<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>This is <a href=\"https:\/\/www.bridgetownrb.com\/docs\/configuration\/initializers#low-level-boot-customization\">documented here<\/a>.<\/p>\n\n<h2 id=\"implementing-posts-new\">Implementing <code class=\"highlighter-rouge\">posts new<\/code><\/h2>\n\n<p>The next step is to actually start implementing the commands.<\/p>\n\n<p>We\u2019ll add the file <code class=\"highlighter-rouge\">lib\/local\/commands\/post.rb<\/code> which will contain the code for\nthe commands with the bare minimum to make it possible to invoke\n<code class=\"highlighter-rouge\">bin\/bridgetown posts<\/code>.<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># lib\/local\/commands\/posts.rb<\/span>\n\n<span class=\"n\">require_all<\/span> <span class=\"s1\">'bridgetown-core\/commands\/concerns'<\/span>\n\n<span class=\"k\">module<\/span> <span class=\"nn\">Local<\/span>\n  <span class=\"k\">module<\/span> <span class=\"nn\">Commands<\/span>\n    <span class=\"k\">class<\/span> <span class=\"nc\">Posts<\/span> <span class=\"o\">&lt;<\/span> <span class=\"no\">Thor<\/span>\n      <span class=\"kp\">include<\/span> <span class=\"no\">Thor<\/span><span class=\"o\">::<\/span><span class=\"no\">Actions<\/span>\n      <span class=\"kp\">include<\/span> <span class=\"no\">Bridgetown<\/span><span class=\"o\">::<\/span><span class=\"no\">Commands<\/span><span class=\"o\">::<\/span><span class=\"no\">ConfigurationOverridable<\/span>\n\n      <span class=\"no\">Bridgetown<\/span><span class=\"o\">::<\/span><span class=\"no\">Commands<\/span><span class=\"o\">::<\/span><span class=\"no\">Registrations<\/span><span class=\"p\">.<\/span><span class=\"nf\">register<\/span> <span class=\"k\">do<\/span>\n        <span class=\"n\">desc<\/span> <span class=\"s1\">'posts &lt;command&gt;'<\/span><span class=\"p\">,<\/span> <span class=\"s1\">'Manage posts'<\/span>\n        <span class=\"n\">subcommand<\/span> <span class=\"s1\">'posts'<\/span><span class=\"p\">,<\/span> <span class=\"no\">Posts<\/span>\n      <span class=\"k\">end<\/span>\n\n      <span class=\"k\">def<\/span> <span class=\"nc\">self<\/span><span class=\"o\">.<\/span><span class=\"nf\">source_root<\/span>\n        <span class=\"no\">File<\/span><span class=\"p\">.<\/span><span class=\"nf\">dirname<\/span><span class=\"p\">(<\/span><span class=\"kp\">__FILE__<\/span><span class=\"p\">)<\/span>\n      <span class=\"k\">end<\/span>\n    <span class=\"k\">end<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>With that it is now possible to call <code class=\"highlighter-rouge\">bin\/bridgetown posts help<\/code>:<\/p>\n\n<div class=\"language-shell highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>~\/nerdd.dk <span class=\"nv\">$ <\/span>bin\/bridgetown posts <span class=\"nb\">help\n<\/span>Commands:\n  bridgetown posts <span class=\"nb\">help<\/span> <span class=\"o\">[<\/span>COMMAND]  <span class=\"c\"># Describe subcommands or one specific subcommand<\/span>\n\nOptions:\n  <span class=\"nt\">-e<\/span>, <span class=\"o\">[<\/span><span class=\"nt\">--environment<\/span><span class=\"o\">=<\/span>ENVIRONMENT]  <span class=\"c\"># The environment used for this command (aka development, test, production, etc.)<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Let\u2019s add the code for generating a basic new post.<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># lib\/local\/commands\/posts.rb<\/span>\n<span class=\"c1\"># ...<\/span>\n<span class=\"nb\">require<\/span> <span class=\"s1\">'pathname'<\/span>\n\n<span class=\"k\">module<\/span> <span class=\"nn\">Local<\/span>\n  <span class=\"k\">module<\/span> <span class=\"nn\">Commands<\/span>\n    <span class=\"k\">class<\/span> <span class=\"nc\">Posts<\/span> <span class=\"o\">&lt;<\/span> <span class=\"no\">Thor<\/span>\n      <span class=\"c1\"># ...<\/span>\n      <span class=\"n\">desc<\/span> <span class=\"s1\">'new TITLE'<\/span><span class=\"p\">,<\/span> <span class=\"s1\">'Add a new post with current date and the given title'<\/span>\n      <span class=\"k\">def<\/span> <span class=\"nf\">new<\/span><span class=\"p\">(<\/span><span class=\"n\">title<\/span><span class=\"p\">)<\/span>\n        <span class=\"n\">time<\/span> <span class=\"o\">=<\/span> <span class=\"no\">Time<\/span><span class=\"p\">.<\/span><span class=\"nf\">now<\/span>\n        <span class=\"n\">filename<\/span> <span class=\"o\">=<\/span> <span class=\"s2\">\"<\/span><span class=\"si\">#{<\/span><span class=\"n\">time<\/span><span class=\"p\">.<\/span><span class=\"nf\">strftime<\/span><span class=\"p\">(<\/span><span class=\"s1\">'%Y-%m-%d'<\/span><span class=\"p\">)<\/span><span class=\"si\">}<\/span><span class=\"s2\">-<\/span><span class=\"si\">#{<\/span><span class=\"no\">Bridgetown<\/span><span class=\"o\">::<\/span><span class=\"no\">Utils<\/span><span class=\"p\">.<\/span><span class=\"nf\">slugify<\/span><span class=\"p\">(<\/span><span class=\"n\">title<\/span><span class=\"p\">)<\/span><span class=\"si\">}<\/span><span class=\"s2\">.md\"<\/span>\n        <span class=\"n\">ctx<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span> <span class=\"n\">time<\/span><span class=\"p\">:,<\/span> <span class=\"ss\">title: <\/span><span class=\"p\">}<\/span>\n        <span class=\"n\">template<\/span><span class=\"p\">(<\/span><span class=\"s1\">'templates\/new_post.md.tt'<\/span><span class=\"p\">,<\/span>\n                 <span class=\"no\">File<\/span><span class=\"p\">.<\/span><span class=\"nf\">join<\/span><span class=\"p\">(<\/span><span class=\"no\">POSTS_DIR<\/span><span class=\"p\">,<\/span> <span class=\"n\">filename<\/span><span class=\"p\">),<\/span>\n                 <span class=\"ss\">context: <\/span><span class=\"n\">ctx<\/span><span class=\"p\">.<\/span><span class=\"nf\">send<\/span><span class=\"p\">(<\/span><span class=\"ss\">:binding<\/span><span class=\"p\">))<\/span>\n      <span class=\"k\">end<\/span>\n\n      <span class=\"kp\">private<\/span>\n\n      <span class=\"no\">POSTS_DIR<\/span> <span class=\"o\">=<\/span> <span class=\"no\">Pathname<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span><span class=\"p\">(<\/span><span class=\"no\">Bridgetown<\/span><span class=\"p\">.<\/span><span class=\"nf\">configuration<\/span><span class=\"p\">.<\/span><span class=\"nf\">source<\/span><span class=\"p\">).<\/span><span class=\"nf\">join<\/span><span class=\"p\">(<\/span><span class=\"s1\">'_posts'<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">end<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>It generates a new file in the <code class=\"highlighter-rouge\">src\/_posts<\/code> directory with the slugified title\nand the current time.<\/p>\n\n<p>It uses the <code class=\"highlighter-rouge\">lib\/local\/commands\/templates\/new_post.md.tt<\/code> template.<\/p>\n\n<div class=\"language-erb highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\">&lt;%# lib\/local\/commands\/templates\/new_post.md.tt %&gt;<\/span>\n---\ntitle: \"<span class=\"cp\">&lt;%=<\/span> <span class=\"n\">title<\/span> <span class=\"cp\">%&gt;<\/span>\"\ndate: Thu, 16 Jan 2025 17:03:01 +0100last_modified_at: Fri, 17 Jan 2025 12:46:55 +0100\nlast_modified_at: Fri, 17 Jan 2025 12:52:58 +0100\npublished: false\n---\n<\/code><\/pre><\/div><\/div>\n\n<p>Let\u2019s continue and add some optional options for setting an author and a list of\ntags.<\/p>\n\n<p>In the command we need to add a couple of <code class=\"highlighter-rouge\">method_option<\/code> lines and pass the\ninput for author and tags along in the context to the template.<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># lib\/local\/commands\/posts.rb<\/span>\n<span class=\"c1\"># ...<\/span>\n<span class=\"n\">desc<\/span> <span class=\"s1\">'new TITLE [options]'<\/span><span class=\"p\">,<\/span> <span class=\"s1\">'Add a new post with current date and the given title'<\/span>\n<span class=\"n\">method_option<\/span> <span class=\"ss\">:author<\/span><span class=\"p\">,<\/span> <span class=\"ss\">type: :string<\/span><span class=\"p\">,<\/span> <span class=\"ss\">aliases: <\/span><span class=\"s1\">'-a'<\/span><span class=\"p\">,<\/span> <span class=\"ss\">desc: <\/span><span class=\"s1\">'author identifier'<\/span>\n<span class=\"n\">method_option<\/span> <span class=\"ss\">:tags<\/span><span class=\"p\">,<\/span> <span class=\"ss\">type: :array<\/span><span class=\"p\">,<\/span> <span class=\"ss\">desc: <\/span><span class=\"s1\">'list of tags'<\/span>\n<span class=\"k\">def<\/span> <span class=\"nf\">new<\/span><span class=\"p\">(<\/span><span class=\"n\">title<\/span><span class=\"p\">)<\/span>\n  <span class=\"n\">time<\/span> <span class=\"o\">=<\/span> <span class=\"no\">Time<\/span><span class=\"p\">.<\/span><span class=\"nf\">now<\/span>\n  <span class=\"n\">author<\/span> <span class=\"o\">=<\/span> <span class=\"n\">options<\/span><span class=\"p\">[<\/span><span class=\"s1\">'author'<\/span><span class=\"p\">]<\/span> <span class=\"o\">||<\/span> <span class=\"s1\">''<\/span>\n  <span class=\"n\">tags<\/span> <span class=\"o\">=<\/span> <span class=\"n\">options<\/span><span class=\"p\">[<\/span><span class=\"s1\">'tags'<\/span><span class=\"p\">]<\/span> <span class=\"o\">||<\/span> <span class=\"p\">[]<\/span>\n  <span class=\"n\">ctx<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span> <span class=\"n\">time<\/span><span class=\"p\">:,<\/span> <span class=\"n\">author<\/span><span class=\"p\">:,<\/span> <span class=\"n\">tags<\/span><span class=\"p\">:,<\/span> <span class=\"ss\">title: <\/span><span class=\"p\">}<\/span>\n  <span class=\"c1\"># ...<\/span>\n<span class=\"k\">end<\/span>\n<span class=\"c1\"># ...<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>And in the template we\u2019ll insert the author and tags if they are not empty.<\/p>\n\n<div class=\"language-erb highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\">&lt;%# lib\/local\/commands\/templates\/new_post.md.tt %&gt;<\/span>\n---\n# ...\npublished: false\n<span class=\"cp\">&lt;%-<\/span> <span class=\"k\">if<\/span> <span class=\"o\">!<\/span><span class=\"n\">author<\/span><span class=\"p\">.<\/span><span class=\"nf\">empty?<\/span> <span class=\"cp\">-%&gt;<\/span>\nauthor: <span class=\"cp\">&lt;%=<\/span> <span class=\"n\">author<\/span> <span class=\"cp\">%&gt;<\/span>\n<span class=\"cp\">&lt;%-<\/span> <span class=\"k\">end<\/span> <span class=\"cp\">-%&gt;<\/span>\n<span class=\"cp\">&lt;%-<\/span> <span class=\"k\">if<\/span> <span class=\"o\">!<\/span><span class=\"n\">tags<\/span><span class=\"p\">.<\/span><span class=\"nf\">empty?<\/span> <span class=\"cp\">-%&gt;<\/span>\ntags: <span class=\"cp\">&lt;%=<\/span> <span class=\"n\">tags<\/span><span class=\"p\">.<\/span><span class=\"nf\">join<\/span><span class=\"p\">(<\/span><span class=\"s1\">' '<\/span><span class=\"p\">)<\/span> <span class=\"cp\">%&gt;<\/span>\n<span class=\"cp\">&lt;%-<\/span> <span class=\"k\">end<\/span> <span class=\"cp\">-%&gt;<\/span>\n---\n<\/code><\/pre><\/div><\/div>\n\n<p>And that is basically it. A new unpublished post can be created with\n<code class=\"highlighter-rouge\">bin\/bt posts new \"My amazing post\" --author=rasmus --tags=this is amazing<\/code><\/p>\n\n<h2 id=\"touching-posts\">Touching posts<\/h2>\n\n<p>The command for touching posts should look like <code class=\"highlighter-rouge\">bin\/bridgetown posts touch FILENAME<\/code>,\nand it should be able to find the correct post with only a partial filename. If\nthe partial filename matches multiple posts, it should just list the found\nfilenames instead of touching everyone of them, and if none matches the given\nfilename, it should also print a helpful message.<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># lib\/local\/commands\/posts.rb<\/span>\n<span class=\"c1\"># ...<\/span>\n<span class=\"n\">desc<\/span> <span class=\"s1\">'touch FILENAME'<\/span><span class=\"p\">,<\/span>\n    <span class=\"s1\">'Updates filename and post date for drafts, sets or updates last_modified_at on published posts'<\/span>\n<span class=\"k\">def<\/span> <span class=\"nf\">touch<\/span><span class=\"p\">(<\/span><span class=\"n\">filename<\/span><span class=\"p\">)<\/span>\n  <span class=\"n\">posts<\/span> <span class=\"o\">=<\/span> <span class=\"n\">find_posts<\/span><span class=\"p\">(<\/span><span class=\"n\">filename<\/span><span class=\"p\">)<\/span>  <span class=\"c1\"># TODO: Implement find_posts<\/span>\n  <span class=\"k\">case<\/span> <span class=\"n\">posts<\/span>\n  <span class=\"k\">in<\/span> <span class=\"p\">[<\/span><span class=\"n\">post<\/span><span class=\"p\">]<\/span>\n    <span class=\"n\">touch_post<\/span><span class=\"p\">(<\/span><span class=\"n\">post<\/span><span class=\"p\">)<\/span> <span class=\"c1\"># TODO: Implement touch_post<\/span>\n  <span class=\"k\">in<\/span> <span class=\"p\">[<\/span><span class=\"n\">_<\/span><span class=\"p\">,<\/span> <span class=\"o\">*<\/span><span class=\"p\">]<\/span>\n    <span class=\"n\">say<\/span> <span class=\"s1\">'Found multiple posts. Name should be unambiguous. Please try again with one of these:'<\/span>\n    <span class=\"n\">say<\/span> <span class=\"n\">posts<\/span><span class=\"p\">.<\/span><span class=\"nf\">sort_by<\/span><span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"ss\">:ctime<\/span><span class=\"p\">).<\/span><span class=\"nf\">reverse<\/span><span class=\"p\">.<\/span><span class=\"nf\">map<\/span> <span class=\"p\">{<\/span> <span class=\"o\">|<\/span><span class=\"nb\">p<\/span><span class=\"o\">|<\/span> <span class=\"s2\">\"<\/span><span class=\"se\">\\t<\/span><span class=\"si\">#{<\/span><span class=\"nb\">p<\/span><span class=\"p\">.<\/span><span class=\"nf\">basename<\/span><span class=\"p\">(<\/span><span class=\"s1\">'.md'<\/span><span class=\"p\">)<\/span><span class=\"si\">}<\/span><span class=\"s2\">\"<\/span> <span class=\"p\">}.<\/span><span class=\"nf\">join<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"<\/span><span class=\"se\">\\n<\/span><span class=\"s2\">\"<\/span><span class=\"p\">)<\/span>\n  <span class=\"k\">else<\/span>\n    <span class=\"n\">say<\/span> <span class=\"s2\">\"Could not find any posts with a file name starting with <\/span><span class=\"se\">\\\"<\/span><span class=\"si\">#{<\/span><span class=\"n\">filename<\/span><span class=\"si\">}<\/span><span class=\"se\">\\\"<\/span><span class=\"s2\">\"<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n  <span class=\"c1\"># ...<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Let\u2019s add the <code class=\"highlighter-rouge\">find_posts<\/code> method next. It simply finds all the files in the\n<code class=\"highlighter-rouge\">src\/_posts<\/code> directory where the filename matches the string containing the\n(possible) partial filename.<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># lib\/local\/commands\/posts.rb<\/span>\n<span class=\"c1\"># ...<\/span>\n<span class=\"kp\">private<\/span>\n\n<span class=\"k\">def<\/span> <span class=\"nf\">find_posts<\/span><span class=\"p\">(<\/span><span class=\"n\">filename<\/span><span class=\"p\">)<\/span>\n  <span class=\"no\">Pathname<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span><span class=\"p\">(<\/span><span class=\"no\">POSTS_DIR<\/span><span class=\"p\">).<\/span><span class=\"nf\">children<\/span><span class=\"p\">(<\/span><span class=\"kp\">false<\/span><span class=\"p\">).<\/span><span class=\"nf\">select<\/span> <span class=\"k\">do<\/span> <span class=\"o\">|<\/span><span class=\"nb\">p<\/span><span class=\"o\">|<\/span>\n    <span class=\"nb\">p<\/span><span class=\"p\">.<\/span><span class=\"nf\">extname<\/span> <span class=\"o\">==<\/span> <span class=\"s1\">'.md'<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"nb\">p<\/span><span class=\"p\">.<\/span><span class=\"nf\">to_s<\/span> <span class=\"o\">=~<\/span> <span class=\"no\">Regexp<\/span><span class=\"p\">.<\/span><span class=\"nf\">new<\/span><span class=\"p\">(<\/span><span class=\"n\">filename<\/span><span class=\"p\">)<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n<span class=\"c1\"># ...<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<p>Touching a post is slightly more complicated than creating a new one. When a post\nis unpublished, it needs to alter the date in filename to the current date and\nupdate the <code class=\"highlighter-rouge\">date<\/code> field in the frontmatter. For published posts, it must leave\nthe filename and <code class=\"highlighter-rouge\">date<\/code> alone and instead set or update the <code class=\"highlighter-rouge\">last_modified_at<\/code>\nfield in the frontmatter.<\/p>\n\n<p>So we\u2019ll split it in two separate steps.<\/p>\n\n<h2 id=\"touching-an-unpublished-post\">Touching an unpublished post<\/h2>\n\n<p>First we\u2019ll implement touching an unpublished post.<\/p>\n\n<p>Since this needs to rename the file, we have to be careful to not overwrite any\nexisting posts. If a post with the new filename already exists, we will back out\nand not do anything. But otherwise the easiest and safest way to do this is to\nfirst make a copy with the new filename, then update the frontmatter and finally\nremoving the old file. If anything goes wrong after copying the file, the new\nfile is deleted, and a message is printed to the console.<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># lib\/local\/commands\/posts.rb<\/span>\n<span class=\"c1\">#...<\/span>\n<span class=\"kp\">private<\/span>\n<span class=\"c1\"># ...<\/span>\n<span class=\"no\">DATETIME_FORMAT<\/span> <span class=\"o\">=<\/span> <span class=\"s1\">'%a, %d %b %Y %T %z'<\/span>\n\n<span class=\"k\">def<\/span> <span class=\"nf\">touch_post<\/span><span class=\"p\">(<\/span><span class=\"n\">file<\/span><span class=\"p\">)<\/span>\n  <span class=\"n\">path<\/span> <span class=\"o\">=<\/span> <span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">expand_path<\/span><span class=\"p\">(<\/span><span class=\"no\">POSTS_DIR<\/span><span class=\"p\">)<\/span>\n  <span class=\"n\">time<\/span> <span class=\"o\">=<\/span> <span class=\"no\">Time<\/span><span class=\"p\">.<\/span><span class=\"nf\">now<\/span>\n  <span class=\"n\">new_filename<\/span> <span class=\"o\">=<\/span> <span class=\"n\">path<\/span><span class=\"p\">.<\/span><span class=\"nf\">basename<\/span><span class=\"p\">.<\/span><span class=\"nf\">to_s<\/span><span class=\"p\">.<\/span><span class=\"nf\">gsub<\/span><span class=\"p\">(<\/span><span class=\"sr\">\/^\\d{4}-\\d{2}-\\d{2}\/<\/span><span class=\"p\">,<\/span> <span class=\"n\">time<\/span><span class=\"p\">.<\/span><span class=\"nf\">strftime<\/span><span class=\"p\">(<\/span><span class=\"s1\">'%Y-%m-%d'<\/span><span class=\"p\">))<\/span>\n  <span class=\"n\">new_path<\/span> <span class=\"o\">=<\/span> <span class=\"no\">POSTS_DIR<\/span><span class=\"p\">.<\/span><span class=\"nf\">join<\/span><span class=\"p\">(<\/span><span class=\"n\">new_filename<\/span><span class=\"p\">)<\/span>\n\n  <span class=\"k\">if<\/span> <span class=\"n\">new_path<\/span><span class=\"p\">.<\/span><span class=\"nf\">exist?<\/span>\n    <span class=\"n\">say<\/span> <span class=\"s2\">\"<\/span><span class=\"si\">#{<\/span><span class=\"n\">new_path<\/span><span class=\"si\">}<\/span><span class=\"s2\"> already exists, aborting.\"<\/span>\n  <span class=\"k\">else<\/span>\n    <span class=\"k\">begin<\/span>\n      <span class=\"n\">copy_file<\/span> <span class=\"n\">path<\/span><span class=\"p\">,<\/span> <span class=\"n\">new_path<\/span>\n      <span class=\"n\">gsub_file<\/span> <span class=\"n\">new_path<\/span><span class=\"p\">,<\/span> <span class=\"sr\">\/^date: .*$\/<\/span><span class=\"p\">,<\/span> <span class=\"s2\">\"date: <\/span><span class=\"si\">#{<\/span><span class=\"n\">time<\/span><span class=\"p\">.<\/span><span class=\"nf\">strftime<\/span><span class=\"p\">(<\/span><span class=\"no\">DATETIME_FORMAT<\/span><span class=\"p\">)<\/span><span class=\"si\">}<\/span><span class=\"s2\">\"<\/span>\n      <span class=\"n\">remove_file<\/span> <span class=\"n\">path<\/span>\n    <span class=\"k\">rescue<\/span> <span class=\"no\">StandardError<\/span>\n      <span class=\"n\">remove_file<\/span> <span class=\"n\">new_path<\/span> <span class=\"k\">if<\/span> <span class=\"n\">new_path<\/span><span class=\"p\">.<\/span><span class=\"nf\">exist?<\/span>\n      <span class=\"n\">say<\/span> <span class=\"s2\">\"Could not touch the post named <\/span><span class=\"si\">#{<\/span><span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">basename<\/span><span class=\"p\">(<\/span><span class=\"s1\">'.md'<\/span><span class=\"p\">)<\/span><span class=\"si\">}<\/span><span class=\"s2\">, aborting.\"<\/span>\n    <span class=\"k\">end<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n<span class=\"c1\"># ...<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<h2 id=\"touching-a-published-post\">Touching a published post<\/h2>\n\n<p>Renaming the file and updating <code class=\"highlighter-rouge\">date<\/code> in the frontmatter is fine for unpublished\nposts, but for posts that are already published, it is not ideal, since renaming\nthe file changes the URL to the post on the built site and makes it appear to\nhave been published at the new date.<\/p>\n\n<p>Instead we only want to set or update <code class=\"highlighter-rouge\">last_modified_at<\/code> in the frontmatter, if\nthe post is published.<\/p>\n\n<p>We\u2019ll extract everything in <code class=\"highlighter-rouge\">touch_post<\/code> except the first line to a new method\ncalled <code class=\"highlighter-rouge\">update_unpublished_post<\/code>, and add a <code class=\"highlighter-rouge\">published?<\/code> method that tells if a\npost is published, and a <code class=\"highlighter-rouge\">set_or_update_last_modified_at<\/code> method for updating\nthe frontmatter.<\/p>\n\n<p><code class=\"highlighter-rouge\">set_or_update_last_modified_at<\/code> will have to figure out if the post has been\nmodified before, which is done by checking if <code class=\"highlighter-rouge\">last_modified_at<\/code> already is set.<\/p>\n\n<div class=\"language-ruby highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c1\"># lib\/local\/commands\/posts.rb<\/span>\n<span class=\"c1\">#...<\/span>\n<span class=\"kp\">private<\/span>\n<span class=\"c1\">#...<\/span>\n<span class=\"k\">def<\/span> <span class=\"nf\">touch_post<\/span><span class=\"p\">(<\/span><span class=\"n\">file<\/span><span class=\"p\">)<\/span>\n  <span class=\"n\">path<\/span> <span class=\"o\">=<\/span> <span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">expand_path<\/span><span class=\"p\">(<\/span><span class=\"no\">POSTS_DIR<\/span><span class=\"p\">)<\/span>\n  <span class=\"k\">if<\/span> <span class=\"n\">published?<\/span><span class=\"p\">(<\/span><span class=\"n\">path<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">set_or_update_last_modified_at<\/span><span class=\"p\">(<\/span><span class=\"n\">path<\/span><span class=\"p\">)<\/span>\n  <span class=\"k\">else<\/span>\n    <span class=\"n\">update_unpublished_post<\/span><span class=\"p\">(<\/span><span class=\"n\">path<\/span><span class=\"p\">)<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n\n<span class=\"k\">def<\/span> <span class=\"nf\">set_or_update_last_modified_at<\/span><span class=\"p\">(<\/span><span class=\"n\">path<\/span><span class=\"p\">)<\/span>\n  <span class=\"k\">if<\/span> <span class=\"n\">modified?<\/span><span class=\"p\">(<\/span><span class=\"n\">path<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">gsub_file<\/span> <span class=\"n\">path<\/span><span class=\"p\">,<\/span> <span class=\"sr\">\/^last_modified_at: .*$\/<\/span><span class=\"p\">,<\/span> <span class=\"s2\">\"last_modified_at: <\/span><span class=\"si\">#{<\/span><span class=\"n\">time<\/span><span class=\"p\">.<\/span><span class=\"nf\">strftime<\/span><span class=\"p\">(<\/span><span class=\"no\">DATETIME_FORMAT<\/span><span class=\"p\">)<\/span><span class=\"si\">}<\/span><span class=\"s2\">\"<\/span>\n  <span class=\"k\">else<\/span>\n    <span class=\"n\">inject_into_file<\/span> <span class=\"n\">path<\/span><span class=\"p\">,<\/span> <span class=\"s2\">\"last_modified_at: <\/span><span class=\"si\">#{<\/span><span class=\"n\">time<\/span><span class=\"p\">.<\/span><span class=\"nf\">strftime<\/span><span class=\"p\">(<\/span><span class=\"no\">DATETIME_FORMAT<\/span><span class=\"p\">)<\/span><span class=\"si\">}<\/span><span class=\"s2\">\"<\/span><span class=\"p\">,<\/span> <span class=\"ss\">after: <\/span><span class=\"sr\">\/^date: .*$\/<\/span>\n  <span class=\"k\">end<\/span>\n<span class=\"k\">end<\/span>\n\n<span class=\"k\">def<\/span> <span class=\"nf\">update_unpublished_post<\/span><span class=\"p\">(<\/span><span class=\"n\">path<\/span><span class=\"p\">)<\/span>\n  <span class=\"c1\">#...<\/span>\n<span class=\"k\">end<\/span>\n\n<span class=\"k\">def<\/span> <span class=\"nf\">modified?<\/span><span class=\"p\">(<\/span><span class=\"n\">file<\/span><span class=\"p\">)<\/span>\n  <span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">each_line<\/span><span class=\"p\">.<\/span><span class=\"nf\">any?<\/span> <span class=\"p\">{<\/span> <span class=\"o\">|<\/span><span class=\"n\">line<\/span><span class=\"o\">|<\/span> <span class=\"n\">line<\/span> <span class=\"o\">=~<\/span> <span class=\"sr\">\/^last_modified_at: .*$\/<\/span> <span class=\"p\">}<\/span>\n<span class=\"k\">end<\/span>\n\n<span class=\"k\">def<\/span> <span class=\"nf\">published?<\/span><span class=\"p\">(<\/span><span class=\"n\">file<\/span><span class=\"p\">)<\/span>\n  <span class=\"n\">file<\/span><span class=\"p\">.<\/span><span class=\"nf\">each_line<\/span><span class=\"p\">.<\/span><span class=\"nf\">any?<\/span> <span class=\"p\">{<\/span> <span class=\"o\">|<\/span><span class=\"n\">line<\/span><span class=\"o\">|<\/span> <span class=\"n\">line<\/span> <span class=\"o\">=~<\/span> <span class=\"sr\">\/^published: true$\/<\/span> <span class=\"p\">}<\/span>\n<span class=\"k\">end<\/span>\n<\/code><\/pre><\/div><\/div>\n\n<h2 id=\"final-thoughts\">Final thoughts<\/h2>\n\n<p>These extra commands are a bit rough around the edges, especially in the way the\nfrontmatter is updated.<\/p>\n\n<p>If there are lines in the body of a post containing only <code class=\"highlighter-rouge\">published: true<\/code> or\n<code class=\"highlighter-rouge\">last_modified_at: &lt;date&gt;<\/code>, they will happily be accepted as part of the\nfrontmatter.<\/p>\n\n<p>The commands also assume the posts are all written in Markdown, and the\nfrontmatter is in YAML.<\/p>\n\n<p>I have also not bothered with writing any tests.<\/p>\n\n<p>If you have made it this far, and you are interested in the code its entirety,\nit is available as a <a href=\"https:\/\/gist.github.com\/rbgrouleff\/67a159c489a15976248a7edb14a0c697\">gist on GitHub<\/a>.<\/p>\n\n<p>Feel free to do with it, what you will.<\/p>\n\n<p>If you make a gem out of it, please <a href=\"https:\/\/mstdn.social\/@nerdd\">ping me<\/a>, I\nwould love to install it. You are also more than welcome to let me know, if I\nhave made some stupid mistakes, or if you have improvements.<\/p>\n","id":"repo:\/\/posts.collection\/_posts\/2025-01-16-adding-a-couple-of-commands-for-managing-posts-in-bridgetown.md","link":{"@attributes":{"href":"https:\/\/nerdd.dk\/2025\/01\/16\/adding-a-couple-of-commands-for-managing-posts-in-bridgetown\/","rel":"alternate","type":"text\/html","title":"Adding a couple of commands for managing posts in Bridgetown"}},"published":"2025-01-16T16:03:01+00:00","summary":"A walkthrough of how I added a couple of commands for managing posts to my Bridgetown project","title":"Adding a couple of commands for managing posts in Bridgetown","updated":"2025-01-17T11:52:58+00:00"},{"author":{"name":"Rasmus Grouleff","uri":"https:\/\/mstdn.social\/@nerdd.dk","email":"rasmus@nerdd.dk"},"category":[{"@attributes":{"term":"meta","label":"tag"}},{"@attributes":{"term":"random","label":"tag"}},{"@attributes":{"term":"personal","label":"tag"}}],"content":"<p><em>Tap tap tap<\/em><\/p>\n\n<p>Hello, is this thing on?<\/p>\n\n<h2 id=\"it-has-actually-happened\">It has actually happened<\/h2>\n\n<p>For years I have wanted to give my little corner of the Internet a fresh lick of paint and perhaps make it a place, where I easily can write about what I find interesting.<\/p>\n\n<p>I have started multiple times on this home improvement project, but I never seemed to be able to finish it \u2013 something about perfect being the enemy of good. Finally, however, I seem to have succeeded in the endeavour.<\/p>\n\n<h2 id=\"a-bit-of-history\">A bit of history<\/h2>\n\n<p>Twenty years ago this website started out as a self-hosted WordPress blog, where I blogged about hobbies and personal stuff. It was back in the gloriuos days with <a href=\"https:\/\/en.wikipedia.org\/wiki\/Digg\">Digg<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/FeedBurner\">Feedburner<\/a>, blogrolls, and slapping AdSense ad panels on your blog so you once in a while could receive a small amount of sweet, sweet Google-money on a cheque.<\/p>\n\n<p>At some point I lost interest in blogging though, and Nerdd.dk ended up being neglected. I moved to different hosts a couple of times, and the database from back then is long gone.<\/p>\n\n<p>That was when I changed it to a small static site that could function as a sort of business card online. And then I neglected it again.<\/p>\n\n<p>Perhaps this time it will be different. I have at least made it easier for myself to publish stuff.<\/p>\n\n<h2 id=\"whats-changed\">What\u2019s changed?<\/h2>\n\n<p>What I have done to set myself up for success (or whatever this might be called) is to use <a href=\"https:\/\/bridgetownrb.com\">Bridgetown<\/a> for HTML generation, and use <a href=\"https:\/\/tailwindcss.com\">Tailwindcss<\/a> for the styling.<\/p>\n\n<p>Bridgetown is a fairly new site generator \u2013 which is capable of a heck of a lot more than what I\u2019m using it for. I have however customised it a bit, but I\u2019ll keep that as a topic for a separate post.<\/p>\n\n<p>For styling I went with Tailwindcss to see what all the fuss is about, and while the long lists of classes in the HTML still irks me out, I have to admit it made it quite easy to make this site not look complete pants. It is even responsive \u2013 and supports dark mode!<\/p>\n\n<h2 id=\"so-are-you-back-to-blogging-again\">So.. are you back to blogging again?<\/h2>\n\n<p>No, not really.<\/p>\n\n<p>I am not planning on writing regularly, and I really can\u2019t be bothered with comments. There is an Atom feed of the posts, however.<\/p>\n\n<h2 id=\"what-to-expect\">What to expect<\/h2>\n\n<p>First of all expect irregular updates.<\/p>\n\n<p>When there are new updates, it will probably be about different aspects of software development, both technical and non-technical.<\/p>\n\n<h2 id=\"thats-it-for-now-i-guess\">That\u2019s it for now, I guess<\/h2>\n\n<p>Well, if you have actually made it this far, I want to thank you for reading along. I don\u2019t know if I would have had the stamina to keep reading.<\/p>\n\n<p>Even though I wrote I couldn\u2019t be bothered with comments, you are however more than welcome to send me an email, if you have any thoughts on what I write, suggestions for topics to write about, or improvements I can make here.<\/p>\n\n<p>Thanks, bye!<\/p>\n","id":"repo:\/\/posts.collection\/_posts\/2024-12-03-is-this-thing-on.md","link":{"@attributes":{"href":"https:\/\/nerdd.dk\/2024\/12\/03\/is-this-thing-on\/","rel":"alternate","type":"text\/html","title":"Is this thing on?"}},"published":"2024-12-03T10:08:13+00:00","summary":"A welcome to my revamped corner of the Internet","title":"Is this thing on?","updated":"2024-12-03T10:08:13+00:00"}]}