From 200a733087c5a9bfc612c79709b51ef00ecd3ae8 Mon Sep 17 00:00:00 2001 From: thiagokokada Date: Thu, 15 Aug 2024 10:14:50 +0000 Subject: [PATCH] README/rss: update --- rss.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rss.xml b/rss.xml index ff9ca85..100ff48 100644 --- a/rss.xml +++ b/rss.xml @@ -13,7 +13,7 @@ Abusing Makefiles for fun and profit https://github.com/thiagokokada/blog/blob/main/2024-08-13/01-abusing-makefiles-for-fun-and-profit.md - <p>If you are following this blog for a while, it should be no surprise that most of the workflow in this blog is <a href="https://github.com/thiagokokada/blog/blob/main/2024-07-29/01-quick-bits-why-you-should-automate-everything.md">automated using Go</a>. I basically write Markdown files with some special rules inside the <a href="https://github.com/thiagokokada/blog">repository</a>, commit and push it. In seconds, the CI (currently <a href="https://github.com/thiagokokada/blog/blob/4e3f25485c6682f3e066b219df2290934bc0d256/.github/workflows/go.yml">GitHub Actions</a>) will take the latest commit, generate some files (since I use the <a href="https://github.com/thiagokokada/blog/blob/main/2024-07-26/02-using-github-as-a-bad-blog-platform.md">repository itself</a> as a backup blog) and publish to the <a href="https://kokada.capivaras.dev/">capivaras.dev website</a>.</p> <p>Now, considering how much about <a href="https://nixos.org/">Nix</a> I talk in this blog, it should be a surprise that the workflow above has <strong>zero</strong> Nix code inside it. I am not saying this blog will never have it, but I am only going to add if this is necessary, for example if I start using a tool to build this blog that I generally don't expect it to be installed by the machine I am currently using. Go is an exception of this rule since it is relatively straightfoward to install (just download the <a href="https://go.dev/doc/install">binary</a>) and because its <a href="https://go.dev/doc/go1compat">stability guarantee</a> means (hopefully) no breakage. But most other things I consider moving targets, and I wouldn't be comfortable to use unless I have Nix to ensure reproducibility.</p> <p>This is why the other tool that this blog (ab)uses during its workflow is <a href="https://en.wikipedia.org/wiki/Make_(software)"><code>Make</code></a>, one of the oldest build automation tool that exist. It is basically available in any *nix (do not confuse with <a href="https://nixos.org/">Nix</a>) system, from most Linux distros to macOS, by default. So it is the tool I choose to automatise some tasks in this blog, even if I consider writing a <code>Makefile</code> (the domain-specific language that <code>Make</code> uses) kind of a lost, dark art.</p> <p>To be clear, the idea of this post is not to be a <code>Makefile</code> tutorial. I will explain some basic concepts, but if you want an actual tutorial a good one can be found <a href="https://makefiletutorial.com/">here</a>. Also, while I am using <code>Make</code> thanks to the reasons above, you can use many other tools for a similar objective, like <a href="https://github.com/casey/just">Justfiles</a>, <a href="https://taskfile.dev/">Taskfiles</a> (sadly it uses <a href="https://github.com/thiagokokada/blog/blob/main/2024-07-31/01-generating-yaml-files-with-nix.md">YAML</a>), or even a small script written in any language you want. The reason that I am writing this post is why you should do it, not how.</p> <p>A quick recap on how this blog works: inside the <a href="https://github.com/thiagokokada/blog">repository</a>, a post is basically a Markdown post following the directory structure below (<a href="https://github.com/thiagokokada/blog/tree/894a388c61ca3a38dfc9d4cbe88dc684fd964bb7">permalink</a> for the current version of this blog):</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>. </span></span><span style="display:flex;"><span>&lt;...&gt; </span></span><span style="display:flex;"><span>├── 2024-08-07 </span></span><span style="display:flex;"><span>│   ├── 01-quick-bits-is-crostini-a-microvm.md </span></span><span style="display:flex;"><span>│   └── 02-meta-are-quick-bits-really-quick.md </span></span><span style="display:flex;"><span>├── 2024-08-11 </span></span><span style="display:flex;"><span>│   └── 01-building-static-binaries-in-nix.md </span></span><span style="display:flex;"><span>├── 2024-08-12 </span></span><span style="display:flex;"><span>│   ├── 01-things-i-dont-like-in-my-chromebook-duet-3.md </span></span><span style="display:flex;"><span>│   └── Screenshot_2024-08-12_20.50.42.png </span></span><span style="display:flex;"><span>├── 2024-08-13 </span></span><span style="display:flex;"><span>│   ├── 01-abusing-makefiles-for-fun-and-profit.md &lt;-- this file </span></span><span style="display:flex;"><span>├── .github </span></span><span style="display:flex;"><span>│   └── workflows </span></span><span style="display:flex;"><span>│   └── go.yml </span></span><span style="display:flex;"><span>├── .gitignore </span></span><span style="display:flex;"><span>├── go.mod </span></span><span style="display:flex;"><span>├── go.sum </span></span><span style="display:flex;"><span>├── LICENSE </span></span><span style="display:flex;"><span>├── link_rewriter.go </span></span><span style="display:flex;"><span>├── Makefile </span></span><span style="display:flex;"><span>├── mataroa.go </span></span><span style="display:flex;"><span>├── README.md </span></span><span style="display:flex;"><span>├── rss.xml </span></span><span style="display:flex;"><span>└── .scripts </span></span><span style="display:flex;"><span> └── gen-post.sh </span></span></code></pre><p>So I just create a new Markdown file following the <code>YYYY-MM-DD/XX-title-slug.md</code> format. It <strong>must</strong> start with a <code>h1</code> header, that will be automatically extract to be used as the post title, but otherwise there is no other formatting rules. It is a highly optionated structure, but the nice thing about being optionated is that we can extract lots of information just from how the files are organised in the filesystem.</p> <p>Most of the magic that converts those Markdown files to actual blog posts are in the Go files that you can see above: <code>blog.go</code> is the main logic that walks in the repository and extracts the necessary information, <code>mataroa.go</code> is responsible for the <a href="https://capivaras.dev/">capivaras.dev</a> integration (that uses <a href="https://mataroa.blog/">Mataroa</a> platform), while <code>link_rewriter.go</code> is responsible to do some transformations in the Markdown files before posting.</p> <p>While I could manage everything by just using <code>go</code> CLI and a few other *nix commands, to make it easier to manager everything I have the following <a href="https://github.com/thiagokokada/blog/blob/e2c30dcc117ba0b95e3df1dbea0be6b5064d5a7e/Makefile"><code>Makefile</code></a>:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>MARKDOWN <span style="color:#f92672">:=</span> <span style="color:#66d9ef">$(</span>wildcard ./**/*.md<span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> all </span></span><span style="display:flex;"><span><span style="color:#a6e22e">all</span><span style="color:#f92672">:</span> README.md rss.xml </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">blog</span><span style="color:#f92672">:</span> *.go go.* </span></span><span style="display:flex;"><span> go build </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">README.md</span><span style="color:#f92672">:</span> blog <span style="color:#66d9ef">$(</span>MARKDOWN<span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span> ./blog &gt; README.md </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">rss.xml</span><span style="color:#f92672">:</span> blog <span style="color:#66d9ef">$(</span>MARKDOWN<span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span> ./blog -rss &gt; rss.xml </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> publish </span></span><span style="display:flex;"><span><span style="color:#a6e22e">publish</span><span style="color:#f92672">:</span> blog </span></span><span style="display:flex;"><span> ./blog -publish </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>DAY <span style="color:#f92672">:=</span> <span style="color:#66d9ef">$(</span>shell date<span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span>_PARSED_DAY <span style="color:#f92672">:=</span> <span style="color:#66d9ef">$(</span>shell date <span style="color:#e6db74">&#39;+%Y-%m-%d&#39;</span> -d <span style="color:#e6db74">&#39;$(DAY)&#39;</span><span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> day </span></span><span style="display:flex;"><span><span style="color:#a6e22e">day</span><span style="color:#f92672">:</span> </span></span><span style="display:flex;"><span> mkdir -p <span style="color:#e6db74">&#39;$(_PARSED_DAY)&#39;</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>TITLE <span style="color:#f92672">=</span> <span style="color:#66d9ef">$(</span>error TITLE is not defined<span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> post </span></span><span style="display:flex;"><span><span style="color:#a6e22e">post</span><span style="color:#f92672">:</span> blog day </span></span><span style="display:flex;"><span> ./.scripts/gen-post.sh <span style="color:#e6db74">&#39;$(_PARSED_DAY)&#39;</span> <span style="color:#e6db74">&#39;$(TITLE)&#39;</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>TITLE <span style="color:#f92672">=</span> <span style="color:#66d9ef">$(</span>error FILE is not defined<span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> draft </span></span><span style="display:flex;"><span><span style="color:#a6e22e">draft</span><span style="color:#f92672">:</span> </span></span><span style="display:flex;"><span> cd <span style="color:#e6db74">&#39;$(dir $(FILE))&#39;</span> <span style="color:#f92672">&amp;&amp;</span> mv <span style="color:#e6db74">&#39;$(notdir $(FILE))&#39;</span> <span style="color:#e6db74">&#39;.$(notdir $(FILE))&#39;</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> undraft </span></span><span style="display:flex;"><span><span style="color:#a6e22e">undraft</span><span style="color:#f92672">:</span> </span></span><span style="display:flex;"><span> cd <span style="color:#e6db74">&#39;$(dir $(FILE))&#39;</span> <span style="color:#f92672">&amp;&amp;</span> mv <span style="color:#e6db74">&#39;$(notdir $(FILE))&#39;</span> <span style="color:#e6db74">&#39;$(patsubst .%,%,$(notdir $(FILE)))&#39;</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> clean </span></span><span style="display:flex;"><span><span style="color:#a6e22e">clean</span><span style="color:#f92672">:</span> </span></span><span style="display:flex;"><span> rm -rf blog </span></span></code></pre><p>For those unfamiliar with <code>Makefile</code>, a quick explanation on how it works from <a href="https://en.wikipedia.org/wiki/Make_(software)#Makefile">Wikipedia</a>:</p> <blockquote> <p>Each rule begins with a <em>dependency line</em> which consists of the rule's target name followed by a colon (:) and optionally a list of targets on which the rule's target depends, its prerequisites.</p> </blockquote> <p>So if we look for example at the <code>blog</code> binary, the dependencies are all the <code>.go</code> files and Go module files like <code>go.mod</code> and <code>go.sum</code>. We can make the <code>blog</code> binary by running:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>$ make blog </span></span><span style="display:flex;"><span>go build </span></span></code></pre><p>One nice thing about <code>Makefile</code> is that they track if any of the source files has a newer timestamp than the target file, and only trigger the build again if there are changes, for example:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>$ make blog </span></span><span style="display:flex;"><span>make: &#39;blog&#39; is up to date. </span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"> </span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ touch blog.go </span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"> </span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ make blog </span></span><span style="display:flex;"><span>go build </span></span></code></pre><p>But sometimes this property is undesirable. In those cases we can declare a target as <code>.PHONY</code>, that basically instructs <code>Makefile</code> to always make the target. One classic example is <code>clean</code> target, that removes build artifacts:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>$ make clean </span></span><span style="display:flex;"><span>rm -rf blog </span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"> </span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ make clean </span></span><span style="display:flex;"><span>rm -rf blog </span></span></code></pre><p>By the way, it is better to declare a target as <code>.PHONY</code> than declaring dependencies incorrectly, especially in languages that has fast build times like e.g.: Go. The worst thing that can happen is something not being rebuild when it needs to. So my recomendation if you are writing your first <code>Makefile</code> is to just declare everything as <code>.PHONY</code>. You can always improve it later.</p> <p>One last basic concept that I want to explain about <code>Makefile</code> is the default target: it is the target that is run if you just run <code>make</code> without arguments inside the directory that contains a <code>Makefile</code>. The default target is generally the first target in the <code>Makefile</code>. It is common to have an <code>all</code> target (that is also marked as <code>.PHONY</code>) that has as dependencies all the targets that you want to build by default. In this particular case I declare the <code>README.md</code> and <code>rss.xml</code> files to be build by default, and they themselves depends in <code>blog</code> binary being build. So once I run <code>make</code> you get as result:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>$ make </span></span><span style="display:flex;"><span>go build </span></span><span style="display:flex;"><span>./blog &gt; README.md </span></span><span style="display:flex;"><span>./blog -rss &gt; rss.xml </span></span></code></pre><p>And this result above highlights the first reason I think you should have a <code>Makefile</code> or something similar in your projects: you don't need to remember the exactly steps that you need to get things working. If I see one project of mine having a <code>Makefile</code>, I can be reasonably confident that I can get it working by just running <code>make</code>.</p> <p>But now let's focus in the other targets that I have in the <code>Makefile</code> that are not related to the build process but are there to help me manage my blog posts. Remember the rules I explained above? Maybe not, but it should be no problem, because:</p> <pre><code>$ make post TITLE=&quot;My new blog post&quot; mkdir -p &quot;2024-08-13&quot; ./.scripts/gen-post.sh &quot;2024-08-13&quot; &quot;My new blog post&quot; Creating file: 2024-08-13/02-my-new-blog-post.md $ cat 2024-08-13/02-my-new-blog-post.md # My new blog post </code></pre> <p>This command, <code>make post</code>, is responsible for:</p> <ol> <li>Create a new directory for today, if it doesn't exist</li> <li>Run the <a href="https://github.com/thiagokokada/blog/blob/6a3b06970729f7650e5bee5fb0e1f9f2541ffea8/.scripts/gen-post.sh"><code>gen-post.sh</code></a> script, that: <ol> <li>Enumerates all posts from the day, so we can number the new post correctly <ul> <li>We already had this post planned for 2024-08-13, so the new post is 02</li> </ul> </li> <li>Slugify the title, so we can create each Markdown file with the correct filename</li> <li>Creates a new Markdown file with the title as a <code>h1</code> header</li> </ol> </li> </ol> <p>The steps above may or may not seen trivial, and for a while I was doing them manually. But not having to think what is the current date or if I already posted that day or what is the slug is for the title make (pun intended) my like much easier.</p> <p>Yes, the code is ugly. The way variables works in <code>Make</code> is that you can declare then inside the <code>Makefile</code>, but they can be overwritten in the terminal if you pass them. I used this to allow <code>make post</code> to also work for future posts:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>$ make post TITLE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Another new blog post&#34;</span> DAY<span style="color:#f92672">=</span>2024-12-12 </span></span><span style="display:flex;"><span>mkdir -p &#34;2024-12-12&#34; </span></span><span style="display:flex;"><span>./.scripts/gen-post.sh &#34;2024-12-12&#34; &#34;Another new blog post&#34; </span></span><span style="display:flex;"><span>Creating file: 2024-12-12/01-another-new-blog-post.md </span></span></code></pre><p>So in the above case, <code>DAY</code> is filled with the value passed in the terminal instead of default (that would be the current day), and <code>_PARSED_DAY</code> is the day we use to actually create the directory. We can actually pass any date format recognised by <a href="https://www.gnu.org/software/coreutils/manual/html_node/Examples-of-date.html"><code>date</code></a>, not just <code>YYYY-MM-DD</code>.</p> <p>I have 2 other phony targets that I want to talk, <code>draft</code> and <code>undraft</code>. They expect a <code>FILE</code> to be passed, and I use them to either hide or unhide a file:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>$ make draft FILE<span style="color:#f92672">=</span>2024-12-12/01-another-new-blog-post.md </span></span><span style="display:flex;"><span>mv &#34;2024-12-12/01-another-new-blog-post.md&#34; &#34;2024-12-12/.01-another-new-blog-post.md&#34; </span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"> </span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ make undraft FILE<span style="color:#f92672">=</span>2024-12-12/.01-another-new-blog-post.md </span></span><span style="display:flex;"><span>mv &#34;2024-12-12/.01-another-new-blog-post.md&#34; &#34;2024-12-12/01-another-new-blog-post.md&#34; </span></span></code></pre><p>Why? Because hidden files are <a href="https://github.com/thiagokokada/blog/blob/894a388c61ca3a38dfc9d4cbe88dc684fd964bb7/blog.go#L101-L104">explicit ignored</a> during my directory parser to mean they're a draft post and not ready to be published. And the reason I created those targets is because I was tired of trying to hide or unhide a file manually.</p> <p>So that's it, for the same reason you <a href="https://github.com/thiagokokada/blog/blob/main/2024-07-29/01-quick-bits-why-you-should-automate-everything.md">should probably automate everything</a>, you also need to have some way to automate your tasks. <code>Makefile</code> is one way to do it, maybe not the best way to do it, but it works and it is available anywhere.</p> + <p>If you are following this blog for a while, it should be no surprise that most of the workflow in this blog is <a href="https://github.com/thiagokokada/blog/blob/main/2024-07-29/01-quick-bits-why-you-should-automate-everything.md">automated using Go</a>. I basically write Markdown files with some special rules inside the <a href="https://github.com/thiagokokada/blog">repository</a>, commit and push it. In seconds, the CI (currently <a href="https://github.com/thiagokokada/blog/blob/4e3f25485c6682f3e066b219df2290934bc0d256/.github/workflows/go.yml">GitHub Actions</a>) will take the latest commit, generate some files (since I use the <a href="https://github.com/thiagokokada/blog/blob/main/2024-07-26/02-using-github-as-a-bad-blog-platform.md">repository itself</a> as a backup blog) and publish to the <a href="https://kokada.capivaras.dev/">capivaras.dev website</a>.</p> <p>Now, considering how much about <a href="https://nixos.org/">Nix</a> I talk in this blog, it should be a surprise that the workflow above has <strong>zero</strong> Nix code inside it. I am not saying this blog will never have it, but I am only going to add if this is necessary, for example if I start using a tool to build this blog that I generally don't expect it to be installed by the machine I am currently using. Go is an exception of this rule since it is relatively straightfoward to install (just download the <a href="https://go.dev/doc/install">binary</a>) and because its <a href="https://go.dev/doc/go1compat">stability guarantee</a> means (hopefully) no breakage. But most other things I consider moving targets, and I wouldn't be comfortable to use unless I have Nix to ensure reproducibility.</p> <p>This is why the other tool that this blog (ab)uses during its workflow is <a href="https://en.wikipedia.org/wiki/Make_(software)"><code>Make</code></a>, one of the oldest build automation tool that exist. It is basically available in any *nix (do not confuse with <a href="https://nixos.org/">Nix</a>) system, from most Linux distros to macOS, by default. So it is the tool I choose to automatise some tasks in this blog, even if I consider writing a <code>Makefile</code> (the domain-specific language that <code>Make</code> uses) kind of a lost, dark art.</p> <p>To be clear, the idea of this post is not to be a <code>Makefile</code> tutorial. I will explain some basic concepts, but if you want an actual tutorial a good one can be found <a href="https://makefiletutorial.com/">here</a>. Also, while I am using <code>Make</code> thanks to the reasons above, you can use many other tools for a similar objective, like <a href="https://github.com/casey/just">Justfiles</a>, <a href="https://taskfile.dev/">Taskfiles</a> (sadly it uses <a href="https://github.com/thiagokokada/blog/blob/main/2024-07-31/01-generating-yaml-files-with-nix.md">YAML</a>), or even a small script written in any language you want. The reason that I am writing this post is why you should do it, not how.</p> <p>A quick recap on how this blog works: inside the <a href="https://github.com/thiagokokada/blog">repository</a>, a post is basically a Markdown post following the directory structure below (<a href="https://github.com/thiagokokada/blog/tree/894a388c61ca3a38dfc9d4cbe88dc684fd964bb7">permalink</a> for the current version of this blog):</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>. </span></span><span style="display:flex;"><span>&lt;...&gt; </span></span><span style="display:flex;"><span>├── 2024-08-07 </span></span><span style="display:flex;"><span>│   ├── 01-quick-bits-is-crostini-a-microvm.md </span></span><span style="display:flex;"><span>│   └── 02-meta-are-quick-bits-really-quick.md </span></span><span style="display:flex;"><span>├── 2024-08-11 </span></span><span style="display:flex;"><span>│   └── 01-building-static-binaries-in-nix.md </span></span><span style="display:flex;"><span>├── 2024-08-12 </span></span><span style="display:flex;"><span>│   ├── 01-things-i-dont-like-in-my-chromebook-duet-3.md </span></span><span style="display:flex;"><span>│   └── Screenshot_2024-08-12_20.50.42.png </span></span><span style="display:flex;"><span>├── 2024-08-13 </span></span><span style="display:flex;"><span>│   ├── 01-abusing-makefiles-for-fun-and-profit.md &lt;-- this file </span></span><span style="display:flex;"><span>├── .github </span></span><span style="display:flex;"><span>│   └── workflows </span></span><span style="display:flex;"><span>│   └── go.yml </span></span><span style="display:flex;"><span>├── .gitignore </span></span><span style="display:flex;"><span>├── go.mod </span></span><span style="display:flex;"><span>├── go.sum </span></span><span style="display:flex;"><span>├── LICENSE </span></span><span style="display:flex;"><span>├── link_rewriter.go </span></span><span style="display:flex;"><span>├── Makefile </span></span><span style="display:flex;"><span>├── mataroa.go </span></span><span style="display:flex;"><span>├── README.md </span></span><span style="display:flex;"><span>├── rss.xml </span></span><span style="display:flex;"><span>└── .scripts </span></span><span style="display:flex;"><span> └── gen-post.sh </span></span></code></pre><p>So I just create a new Markdown file following the <code>YYYY-MM-DD/XX-title-slug.md</code> format. It <strong>must</strong> start with a <code>h1</code> header, that will be automatically extract to be used as the post title, but otherwise there is no other formatting rules. It is a highly optionated structure, but the nice thing about being optionated is that we can extract lots of information just from how the files are organised in the filesystem.</p> <p>Most of the magic that converts those Markdown files to actual blog posts are in the Go files that you can see above: <code>blog.go</code> is the main logic that walks in the repository and extracts the necessary information, <code>mataroa.go</code> is responsible for the <a href="https://capivaras.dev/">capivaras.dev</a> integration (that uses <a href="https://mataroa.blog/">Mataroa</a> platform), while <code>link_rewriter.go</code> is responsible to do some transformations in the Markdown files before posting.</p> <p>While I could manage everything by just using <code>go</code> CLI and a few other *nix commands, to make it easier to manager everything I have the following <a href="https://github.com/thiagokokada/blog/blob/527466a2a7c8baae532281bff5db3f0695f018cb/Makefile"><code>Makefile</code></a>:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>MARKDOWN <span style="color:#f92672">:=</span> <span style="color:#66d9ef">$(</span>wildcard ./**/*.md<span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> all </span></span><span style="display:flex;"><span><span style="color:#a6e22e">all</span><span style="color:#f92672">:</span> README.md rss.xml </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">blog</span><span style="color:#f92672">:</span> *.go go.* </span></span><span style="display:flex;"><span> go build </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">README.md</span><span style="color:#f92672">:</span> blog <span style="color:#66d9ef">$(</span>MARKDOWN<span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span> ./blog &gt; README.md </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">rss.xml</span><span style="color:#f92672">:</span> blog <span style="color:#66d9ef">$(</span>MARKDOWN<span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span> ./blog -rss &gt; rss.xml </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> publish </span></span><span style="display:flex;"><span><span style="color:#a6e22e">publish</span><span style="color:#f92672">:</span> blog </span></span><span style="display:flex;"><span> ./blog -publish </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>DAY <span style="color:#f92672">:=</span> <span style="color:#66d9ef">$(</span>shell date<span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span>_PARSED_DAY <span style="color:#f92672">:=</span> <span style="color:#66d9ef">$(</span>shell date <span style="color:#e6db74">&#39;+%Y-%m-%d&#39;</span> -d <span style="color:#e6db74">&#39;$(DAY)&#39;</span><span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> day </span></span><span style="display:flex;"><span><span style="color:#a6e22e">day</span><span style="color:#f92672">:</span> </span></span><span style="display:flex;"><span> mkdir -p <span style="color:#e6db74">&#39;$(_PARSED_DAY)&#39;</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>TITLE <span style="color:#f92672">=</span> <span style="color:#66d9ef">$(</span>error TITLE is not defined<span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> post </span></span><span style="display:flex;"><span><span style="color:#a6e22e">post</span><span style="color:#f92672">:</span> blog day </span></span><span style="display:flex;"><span> ./.scripts/gen-post.sh <span style="color:#e6db74">&#39;$(_PARSED_DAY)&#39;</span> <span style="color:#e6db74">&#39;$(TITLE)&#39;</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span>FILE <span style="color:#f92672">=</span> <span style="color:#66d9ef">$(</span>error FILE is not defined<span style="color:#66d9ef">)</span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> draft </span></span><span style="display:flex;"><span><span style="color:#a6e22e">draft</span><span style="color:#f92672">:</span> </span></span><span style="display:flex;"><span> cd <span style="color:#e6db74">&#39;$(dir $(FILE))&#39;</span> <span style="color:#f92672">&amp;&amp;</span> mv <span style="color:#e6db74">&#39;$(notdir $(FILE))&#39;</span> <span style="color:#e6db74">&#39;.$(notdir $(FILE))&#39;</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> undraft </span></span><span style="display:flex;"><span><span style="color:#a6e22e">undraft</span><span style="color:#f92672">:</span> </span></span><span style="display:flex;"><span> cd <span style="color:#e6db74">&#39;$(dir $(FILE))&#39;</span> <span style="color:#f92672">&amp;&amp;</span> mv <span style="color:#e6db74">&#39;$(notdir $(FILE))&#39;</span> <span style="color:#e6db74">&#39;$(patsubst .%,%,$(notdir $(FILE)))&#39;</span> </span></span><span style="display:flex;"><span> </span></span><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> clean </span></span><span style="display:flex;"><span><span style="color:#a6e22e">clean</span><span style="color:#f92672">:</span> </span></span><span style="display:flex;"><span> rm -rf blog </span></span></code></pre><p>For those unfamiliar with <code>Makefile</code>, a quick explanation on how it works from <a href="https://en.wikipedia.org/wiki/Make_(software)#Makefile">Wikipedia</a>:</p> <blockquote> <p>Each rule begins with a <em>dependency line</em> which consists of the rule's target name followed by a colon (:) and optionally a list of targets on which the rule's target depends, its prerequisites.</p> </blockquote> <p>So if we look for example at the <code>blog</code> binary, the dependencies are all the <code>.go</code> files and Go module files like <code>go.mod</code> and <code>go.sum</code>. We can make the <code>blog</code> binary by running:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>$ make blog </span></span><span style="display:flex;"><span>go build </span></span></code></pre><p>One nice thing about <code>Makefile</code> is that they track if any of the source files has a newer timestamp than the target file, and only trigger the build again if there are changes, for example:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>$ make blog </span></span><span style="display:flex;"><span>make: &#39;blog&#39; is up to date. </span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"> </span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ touch blog.go </span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"> </span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ make blog </span></span><span style="display:flex;"><span>go build </span></span></code></pre><p>But sometimes this property is undesirable. In those cases we can declare a target as <code>.PHONY</code>, that basically instructs <code>Makefile</code> to always make the target. One classic example is <code>clean</code> target, that removes build artifacts:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>$ make clean </span></span><span style="display:flex;"><span>rm -rf blog </span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"> </span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ make clean </span></span><span style="display:flex;"><span>rm -rf blog </span></span></code></pre><p>By the way, it is better to declare a target as <code>.PHONY</code> than declaring dependencies incorrectly, especially in languages that has fast build times like e.g.: Go. The worst thing that can happen is something not being rebuild when it needs to. So my recomendation if you are writing your first <code>Makefile</code> is to just declare everything as <code>.PHONY</code>. You can always improve it later.</p> <p>One last basic concept that I want to explain about <code>Makefile</code> is the default target: it is the target that is run if you just run <code>make</code> without arguments inside the directory that contains a <code>Makefile</code>. The default target is generally the first target in the <code>Makefile</code>. It is common to have an <code>all</code> target (that is also marked as <code>.PHONY</code>) that has as dependencies all the targets that you want to build by default. In this particular case I declare the <code>README.md</code> and <code>rss.xml</code> files to be build by default, and they themselves depends in <code>blog</code> binary being build. So once I run <code>make</code> you get as result:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>$ make </span></span><span style="display:flex;"><span>go build </span></span><span style="display:flex;"><span>./blog &gt; README.md </span></span><span style="display:flex;"><span>./blog -rss &gt; rss.xml </span></span></code></pre><p>And this result above highlights the first reason I think you should have a <code>Makefile</code> or something similar in your projects: you don't need to remember the exactly steps that you need to get things working. If I see one project of mine having a <code>Makefile</code>, I can be reasonably confident that I can get it working by just running <code>make</code>.</p> <p>But now let's focus in the other targets that I have in the <code>Makefile</code> that are not related to the build process but are there to help me manage my blog posts. Remember the rules I explained above? Maybe not, but it should be no problem, because:</p> <pre><code>$ make post TITLE=&quot;My new blog post&quot; mkdir -p &quot;2024-08-13&quot; ./.scripts/gen-post.sh &quot;2024-08-13&quot; &quot;My new blog post&quot; Creating file: 2024-08-13/02-my-new-blog-post.md $ cat 2024-08-13/02-my-new-blog-post.md # My new blog post </code></pre> <p>This command, <code>make post</code>, is responsible for:</p> <ol> <li>Create a new directory for today, if it doesn't exist</li> <li>Run the <a href="https://github.com/thiagokokada/blog/blob/6a3b06970729f7650e5bee5fb0e1f9f2541ffea8/.scripts/gen-post.sh"><code>gen-post.sh</code></a> script, that: <ol> <li>Enumerates all posts from the day, so we can number the new post correctly <ul> <li>We already had this post planned for 2024-08-13, so the new post is 02</li> </ul> </li> <li>Slugify the title, so we can create each Markdown file with the correct filename</li> <li>Creates a new Markdown file with the title as a <code>h1</code> header</li> </ol> </li> </ol> <p>The steps above may or may not seen trivial, and for a while I was doing them manually. But not having to think what is the current date or if I already posted that day or what is the slug is for the title make (pun intended) my like much easier.</p> <p>Yes, the code is ugly. The way variables works in <code>Make</code> is that you can declare then inside the <code>Makefile</code>, but they can be overwritten in the terminal if you pass them. I used this to allow <code>make post</code> to also work for future posts:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>$ make post TITLE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Another new blog post&#34;</span> DAY<span style="color:#f92672">=</span>2024-12-12 </span></span><span style="display:flex;"><span>mkdir -p &#34;2024-12-12&#34; </span></span><span style="display:flex;"><span>./.scripts/gen-post.sh &#34;2024-12-12&#34; &#34;Another new blog post&#34; </span></span><span style="display:flex;"><span>Creating file: 2024-12-12/01-another-new-blog-post.md </span></span></code></pre><p>So in the above case, <code>DAY</code> is filled with the value passed in the terminal instead of default (that would be the current day), and <code>_PARSED_DAY</code> is the day we use to actually create the directory. We can actually pass any date format recognised by <a href="https://www.gnu.org/software/coreutils/manual/html_node/Examples-of-date.html"><code>date</code></a>, not just <code>YYYY-MM-DD</code>.</p> <p>I have 2 other phony targets that I want to talk, <code>draft</code> and <code>undraft</code>. They expect a <code>FILE</code> to be passed, and I use them to either hide or unhide a file:</p> <pre tabindex="0" style="color:#f8f8f2;background-color:#272822;"><code><span style="display:flex;"><span>$ make draft FILE<span style="color:#f92672">=</span>2024-12-12/01-another-new-blog-post.md </span></span><span style="display:flex;"><span>mv &#34;2024-12-12/01-another-new-blog-post.md&#34; &#34;2024-12-12/.01-another-new-blog-post.md&#34; </span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"> </span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span>$ make undraft FILE<span style="color:#f92672">=</span>2024-12-12/.01-another-new-blog-post.md </span></span><span style="display:flex;"><span>mv &#34;2024-12-12/.01-another-new-blog-post.md&#34; &#34;2024-12-12/01-another-new-blog-post.md&#34; </span></span></code></pre><p>Why? Because hidden files are <a href="https://github.com/thiagokokada/blog/blob/894a388c61ca3a38dfc9d4cbe88dc684fd964bb7/blog.go#L101-L104">explicit ignored</a> during my directory parser to mean they're a draft post and not ready to be published. And the reason I created those targets is because I was tired of trying to hide or unhide a file manually.</p> <p>So that's it, for the same reason you <a href="https://github.com/thiagokokada/blog/blob/main/2024-07-29/01-quick-bits-why-you-should-automate-everything.md">should probably automate everything</a>, you also need to have some way to automate your tasks. <code>Makefile</code> is one way to do it, maybe not the best way to do it, but it works and it is available anywhere.</p> https://github.com/thiagokokada/blog/blob/main/2024-08-13/01-abusing-makefiles-for-fun-and-profit.md Tue, 13 Aug 2024 00:00:00 +0000