<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Aashutosh&#x27;s Devlog</title>
    <subtitle>Thoughts and experiments of a budding engineer.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://aashutosh.pages.dev/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://aashutosh.pages.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2025-05-30T00:00:00+00:00</updated>
    <id>https://aashutosh.pages.dev/atom.xml</id>
    <entry xml:lang="en">
        <title>TIL: Postgres Distinct On Clause</title>
        <published>2025-05-30T00:00:00+00:00</published>
        <updated>2025-05-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Aashutosh A.
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://aashutosh.pages.dev/postgres-distinct-on/"/>
        <id>https://aashutosh.pages.dev/postgres-distinct-on/</id>
        
        <content type="html" xml:base="https://aashutosh.pages.dev/postgres-distinct-on/">&lt;p&gt;I stumbled upon a really handy feature in Postgres today: &lt;code&gt;DISTINCT ON&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;I needed to find the latest entry from a history table for each record in a main table.
The history table tracks changes over time (like status updates or edits), so naturally, there are multiple entries per record.&lt;&#x2F;p&gt;
&lt;p&gt;My first instinct was to go with the usual &lt;code&gt;ROW_NUMBER() OVER (PARTITION BY ...)&lt;&#x2F;code&gt; approach.
It works fine. You partition by the record ID, order by timestamp descending, and filter to the first row.
However, I found a more concise way to do the same thing: using &lt;code&gt;DISTINCT ON&lt;&#x2F;code&gt; clauses.&lt;&#x2F;p&gt;
&lt;p&gt;With &lt;code&gt;DISTINCT ON&lt;&#x2F;code&gt;, you can tell Postgres to return only the first row of each group based on how you sort the data.
For example, if you want the latest entry for each &lt;code&gt;record_id&lt;&#x2F;code&gt;, you can write:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sql&quot; class=&quot;language-sql &quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;SELECT DISTINCT ON (record_id) *
FROM history
ORDER BY record_id, updated_at DESC;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This gives you one row per &lt;code&gt;record_id&lt;&#x2F;code&gt;, and because we are ordering by &lt;code&gt;updated_at DESC&lt;&#x2F;code&gt;, it returns the most recent one.&lt;&#x2F;p&gt;
&lt;p&gt;Here’s the &lt;a href=&quot;https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;current&#x2F;sql-select.html#SQL-DISTINCT&quot;&gt;official Postgres docs on DISTINCT&lt;&#x2F;a&gt; if you want to dive deeper.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>TIL: Using CASE directly in ORDER BY for custom sorting</title>
        <published>2024-01-13T00:00:00+00:00</published>
        <updated>2024-01-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Aashutosh A.
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://aashutosh.pages.dev/order-by-case-clause/"/>
        <id>https://aashutosh.pages.dev/order-by-case-clause/</id>
        
        <content type="html" xml:base="https://aashutosh.pages.dev/order-by-case-clause/">&lt;p&gt;I’ve been using &lt;code&gt;CASE&lt;&#x2F;code&gt; statements in &lt;code&gt;SELECT&lt;&#x2F;code&gt; clauses for a while now, usually to compute some value that I would then use in &lt;code&gt;ORDER BY&lt;&#x2F;code&gt;.
But that often meant wrapping things in a CTE just to clean up the result later.&lt;&#x2F;p&gt;
&lt;p&gt;Today I realized that you can just use the &lt;code&gt;CASE&lt;&#x2F;code&gt; directly in the &lt;code&gt;ORDER BY&lt;&#x2F;code&gt; clause.
No need for a CTE or alias.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sql&quot; class=&quot;language-sql &quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;-- Sorting by priority in a task manager
SELECT title, priority
FROM tasks
ORDER BY
  CASE priority
    WHEN &amp;#x27;Urgent&amp;#x27; THEN 1
    WHEN &amp;#x27;High&amp;#x27; THEN 2
    WHEN &amp;#x27;Medium&amp;#x27; THEN 3
    WHEN &amp;#x27;Low&amp;#x27; THEN 4
  END;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Handling command line arguments in Zig</title>
        <published>2023-06-17T00:00:00+00:00</published>
        <updated>2023-06-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Aashutosh A.
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://aashutosh.pages.dev/cli-args-zig/"/>
        <id>https://aashutosh.pages.dev/cli-args-zig/</id>
        
        <content type="html" xml:base="https://aashutosh.pages.dev/cli-args-zig/">&lt;p&gt;In Zig, handling command line arguments is not as straight forward as with other programming languages.
Since there are no hidden memory allocations, we need to explicitly allocate memory for storing the command line arguments.
This is different from something like in C, where the &lt;code&gt;argv&lt;&#x2F;code&gt; parameter that holds the command line arguments is allocated and populated by the language runtime.&lt;&#x2F;p&gt;
&lt;p&gt;We can read in the arguments from the command line using the &lt;code&gt;argsAlloc&lt;&#x2F;code&gt; function from the &lt;code&gt;std.process&lt;&#x2F;code&gt; namespace in the standard library.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;zig&quot; class=&quot;language-zig &quot;&gt;&lt;code class=&quot;language-zig&quot; data-lang=&quot;zig&quot;&gt;fn argsAlloc(allocator: Allocator) ![][:0]u8
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We see that &lt;code&gt;argsAlloc&lt;&#x2F;code&gt; requires an allocator.
So we first need to create an allocator and pass it to &lt;code&gt;argsAlloc&lt;&#x2F;code&gt; for it to be able to read in the cli arguments.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;zig&quot; class=&quot;language-zig &quot;&gt;&lt;code class=&quot;language-zig&quot; data-lang=&quot;zig&quot;&gt;var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();

const allocator = arena.allocator();
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Here we instantiate an arena allocator that manages the memory provided by the &lt;code&gt;page_allocator&lt;&#x2F;code&gt; which allocates memory in pages.
Zig has a number of different allocator for different use cases.
We use the arena allocator since it provides a simple and efficient way to allocate and deallocate memory.
Benjamin Feng&#x27;s talk &quot;&lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=vHWiDx_l4V0&quot;&gt;What&#x27;s a Memory Allocator Anyway?&lt;&#x2F;a&gt;&quot; provides a really good explanation on the matter.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;defer&lt;&#x2F;code&gt; statement ensures that the allocator is deinitialized when the current scope exits.&lt;&#x2F;p&gt;
&lt;p&gt;With the allocator in place, we can now read in arguments from the command line into a variable as:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;zig&quot; class=&quot;language-zig &quot;&gt;&lt;code class=&quot;language-zig&quot; data-lang=&quot;zig&quot;&gt;const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;putting-it-all-together&quot;&gt;Putting it all together&lt;&#x2F;h2&gt;
&lt;pre data-lang=&quot;zig&quot; class=&quot;language-zig &quot;&gt;&lt;code class=&quot;language-zig&quot; data-lang=&quot;zig&quot;&gt;const std = @import(&amp;quot;std&amp;quot;);

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    const allocator = arena.allocator();

    const args = try std.process.argsAlloc(allocator);
    defer std.process.argsFree(allocator, args);

    if (args.len &amp;lt; 2) {
        std.debug.print(&amp;quot;Hello, stranger!\n&amp;quot;, .{});
    } else {
        std.debug.print(&amp;quot;Hello, {s}!\n&amp;quot;, .{args[1]});
    }
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;aashutosh.pages.dev&#x2F;cli-args-zig&#x2F;cli-zig.png&quot; alt=&quot;Parsing CLI arguments in Zig&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>One month with the Colemak keyboard layout</title>
        <published>2023-02-07T00:00:00+00:00</published>
        <updated>2023-02-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Aashutosh A.
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://aashutosh.pages.dev/colemak-experience/"/>
        <id>https://aashutosh.pages.dev/colemak-experience/</id>
        
        <content type="html" xml:base="https://aashutosh.pages.dev/colemak-experience/">&lt;p&gt;At the start of June, I switched to the &lt;a href=&quot;https:&#x2F;&#x2F;colemak.com&#x2F;&quot;&gt;Colemak&lt;&#x2F;a&gt; keyboard layout.
Colemak is an alternative to the traditional Qwerty layout designed to make typing more efficient and comfortable by placing the most frequently used letters on the home row.
The reason behind the switch was not to improve speed, but to get a more comfortable typing experience.&lt;&#x2F;p&gt;
&lt;p&gt;The reason I switched to Colemak, as opposed to other layouts such as Dvorak, was its similarity to Qwerty - only 2 keys move between hands and also the position of common shortcut keys (z, x, c, v) is preserved which makes transitioning a lot easier.&lt;&#x2F;p&gt;
&lt;p&gt;Due to the similarity between Qwerty and Colemak, memorizing the key placements wasn&#x27;t much of a difficult task.
The real difficulty was in slowing down and not letting muscle memory take over.
Going from typing at around 90 wpm to having to think about the placement of each key was hard, way more than I had imagined.
It took me about a week to get used to and within two weeks I was typing at around 20 wpm.&lt;&#x2F;p&gt;
&lt;p&gt;A month in and I am nearly at 40 wpm.
I have not noticed any significant improvement in typing comfort, most probably because I have only been typing small chunks of text for a short period of time.
However, it does feel satisfying to type words that can be typed using only the home row keys, which is quite a few words.
Contrary to what most people say, the transition has slowly started to affect my muscle memory when typing on my phone as well.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pain-points&quot;&gt;Pain points&lt;&#x2F;h2&gt;
&lt;p&gt;Vim.
I use vim for all my writing and text editing.
Also, most of the programs I use have Vim bindings.
Vim, however, is designed with Qwerty in mind.&lt;&#x2F;p&gt;
&lt;p&gt;Initially I just remapped the bindings to make use of the placement rather than the actual keys (eg. navigation remapped from &lt;code&gt;jkl;&lt;&#x2F;code&gt; to &lt;code&gt;neio&lt;&#x2F;code&gt;) but it started getting confusing and hindering my progress with Colemak.
So I reverted to using them with the default Colemak layout.
Using Vim, and programs that make use of the Vim bindings, with the Colemak layout did not feel as good as it did with Qwerty.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-next&quot;&gt;What next?&lt;&#x2F;h2&gt;
&lt;p&gt;Goodbye Qwerty.
One month with Colemak and I am convinced that it is a much better layout than Qwerty.
Typing is something that I will be doing a lot throughout my life since both my work and hobbies involve computers.
Not just writing code, but things like taking notes, chatting, and emails which I do quite a lot.
Taking this into consideration, I think it only makes sense to invest in a typing layout that is &lt;em&gt;designed&lt;&#x2F;em&gt; to be comfortable.&lt;&#x2F;p&gt;
&lt;p&gt;All these reasoning aside, learning Colemak has been really fun and I have found something new that I want to get good at.
So here&#x27;s to a long ride with Colemak!&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Bootstrapping dev environment setup with Airflow</title>
        <published>2022-12-04T00:00:00+00:00</published>
        <updated>2022-12-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Aashutosh A.
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://aashutosh.pages.dev/dev-env-setup-with-airflow/"/>
        <id>https://aashutosh.pages.dev/dev-env-setup-with-airflow/</id>
        
        <content type="html" xml:base="https://aashutosh.pages.dev/dev-env-setup-with-airflow/">&lt;div class=&quot;notice&quot;&gt;This is just an experiment.
There are tools out there, like Ansible, that handle tasks like these much better.&lt;&#x2F;div&gt;
&lt;p&gt;I recently came across an interesting video on Youtube titled &lt;a href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=YQ056EKzCyw&quot;&gt;Don&#x27;t Use Apache Airflow&lt;&#x2F;a&gt;.
This video talks about Airflow&#x27;s identity crisis—how it&#x27;s promoted as an ETL tool when in fact, it has no ETL specific functionalities.&lt;&#x2F;p&gt;
&lt;p&gt;When starting out, I too got the impression that Airflow was an ETL tool.
It was a while until I realized that it had no ETL specific functionalities and that it was just a highly sophisticated job scheduler.
So this little experiment is an attempt to shed light on the fact that Airflow is just a job scheduler and hence can be used for &lt;em&gt;any&lt;&#x2F;em&gt; kind of scheduled work.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-workflow&quot;&gt;The Workflow&lt;&#x2F;h2&gt;
&lt;p&gt;The goal here is to bootstrap my dev environment setup.
A major portion of it includes installing necessary packages and setting them up the way I configured them.
So, breaking it down, the tasks that need to be accomplished are:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Install git.&lt;&#x2F;li&gt;
&lt;li&gt;Install necessary packages.&lt;&#x2F;li&gt;
&lt;li&gt;Clone my dotfiles repository.&lt;&#x2F;li&gt;
&lt;li&gt;Stow the dotfiles.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;I separate installing git from installing other packages as, while not in this case, I use git to clone programs to build them from source.&lt;&#x2F;p&gt;
&lt;p&gt;Once git is installed, installing other packages and cloning the dotfiles repository can happen in parallel as there is no dependency between the two tasks.
Finally, once the necessary packages are installed and the dotfiles are cloned, the dotfiles are symlinked to the required locations using GNU Stow.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-code&quot;&gt;The Code&lt;&#x2F;h2&gt;
&lt;p&gt;Airflow represents workflows as directed acyclic graphs (DAGs).
The acyclic property prevents us from running into circular dependencies between tasks.&lt;&#x2F;p&gt;
&lt;p&gt;Let&#x27;s define a DAG to represent the workflow.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;py&quot; class=&quot;language-py &quot;&gt;&lt;code class=&quot;language-py&quot; data-lang=&quot;py&quot;&gt;dag = DAG(
    dag_id=&amp;quot;dev_env_setup_dag&amp;quot;,
    start_date=airflow.utils.dates.days_ago(0),
    schedule_interval=None,
)
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since the workflow is run once during initial setup, the &lt;code&gt;schedule_interval&lt;&#x2F;code&gt; for the dag is set to &lt;code&gt;None&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Now defining the tasks specified above as tasks associated with the DAG we just created.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;py&quot; class=&quot;language-py &quot;&gt;&lt;code class=&quot;language-py&quot; data-lang=&quot;py&quot;&gt;install_git = BashOperator(
    task_id=&amp;quot;install_git&amp;quot;,
    bash_command=&amp;quot;echo $PASSWORD | sudo -S dnf install git&amp;quot;,
    dag=dag,
)

clone_dotfiles_repo = BashOperator(
    task_id=&amp;quot;clone_dotfiles_repo&amp;quot;,
    bash_command=&amp;quot;git clone https:&amp;#x2F;&amp;#x2F;codeberg.org&amp;#x2F;chaoticenginerd&amp;#x2F;infra.git $HOME&amp;#x2F;infra&amp;quot;,
    dag=dag,
)

# Specify the packages to install
packages = [
    &amp;quot;neovim&amp;quot;,
    &amp;quot;starship&amp;quot;,
    &amp;quot;stow&amp;quot;,
    &amp;quot;tmux&amp;quot;,
    &amp;quot;trash-cli&amp;quot;,
]

install_packages = BashOperator(
    task_id=&amp;quot;install_packages&amp;quot;,
    bash_command=f&amp;quot;echo $PASSWORD | sudo -S dnf install {&amp;#x27; &amp;#x27;.join(packages)}&amp;quot;,
    dag=dag,
)

# Specifying the dotfiles to stow
stow_dirs = [
    &amp;quot;bin&amp;quot;,
    &amp;quot;nvim&amp;quot;,
    &amp;quot;starship&amp;quot;,
    &amp;quot;tmux&amp;quot;,
]
stow_dotfiles = BashOperator(
    task_id=&amp;quot;stow_dotfiles&amp;quot;,
    bash_command=f&amp;quot;cd $HOME&amp;#x2F;infra&amp;#x2F;dotfiles&amp;#x2F; &amp;amp;&amp;amp; stow -t ~ {&amp;#x27; &amp;#x27;.join(stow_dirs)}&amp;quot;,
    dag=dag,
)
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With all the tasks specified and associated with our DAG, we now specify the order in which these tasks are supposed to run.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;py&quot; class=&quot;language-py &quot;&gt;&lt;code class=&quot;language-py&quot; data-lang=&quot;py&quot;&gt;tasks = [install_git, [clone_dotfiles_repo, install_packages], stow_dotfiles]
airflow.models.baseoperator.chain(*tasks)
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;clone_dotfiles_repo&lt;&#x2F;code&gt; and &lt;code&gt;install_packages&lt;&#x2F;code&gt; are grouped together to specify that they are to be run in parallel.&lt;&#x2F;p&gt;
&lt;p&gt;The complete code for the DAG can be found over on &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;oahshtsua&#x2F;dataswamp&#x2F;blob&#x2F;main&#x2F;pipelines&#x2F;dev_env_setup_dag.py&quot;&gt;Github&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-result&quot;&gt;The Result&lt;&#x2F;h2&gt;
&lt;p&gt;This is what the bootstrap DAG looks like.
&lt;img src=&quot;https:&#x2F;&#x2F;aashutosh.pages.dev&#x2F;dev-env-setup-with-airflow&#x2F;dag.png&quot; alt=&quot;Development environment setup DAG&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A successful run of the DAG will now install the necessary packages, clone my dotfiles repository, and stow my dotfiles to configure my development environment and programs the way I like them.&lt;&#x2F;p&gt;
&lt;p&gt;Once again, I would like to mention that this is not very efficient.
There are tools out there, like Ansible, which do a much better job for tasks like these.
This was just an experiment to try and use Airflow for tasks other than those related to data pipelines.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Embracing the &#x27;bloat&#x27;</title>
        <published>2022-06-27T00:00:00+00:00</published>
        <updated>2022-06-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Aashutosh A.
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://aashutosh.pages.dev/embracing-the-bloat/"/>
        <id>https://aashutosh.pages.dev/embracing-the-bloat/</id>
        
        <content type="html" xml:base="https://aashutosh.pages.dev/embracing-the-bloat/">&lt;p&gt;After riding the minimalism train for a while, I have decided to drop off and settle in the land of a full featured desktop environment - KDE Plasma.&lt;&#x2F;p&gt;
&lt;p&gt;Getting on the &quot;anti-bloat&quot; train, I replaced all my apps with minimal alternatives.
While these apps were fun to use, very customizable, and light on system resources, they came at a cost - my time.
I was spending too much time, more than I had wanted and imagined, to configure them to fit my needs, to create my custom workflow and a coherent system.
All I found myself doing was editing config files out which I wasn&#x27;t learning much and my Linux system became a distraction.&lt;&#x2F;p&gt;
&lt;p&gt;Being obsessed with &quot;anti-bloat&quot; and &quot;minimalism&quot;, what I had forgotten was there were already apps and desktops out there, all free software, that fit my needs and worked out of the box.
I had forgotten that there were lots of other stuff to learn and do than just edit config files.&lt;&#x2F;p&gt;
&lt;p&gt;Few weeks back, I messed up my Debian system while experimenting with the nix package manager and decided to do a clean install.
This time I decided to give KDE Plasma a try and reluctantly pressed &#x27;y&#x27; on the install prompt that asked me if I wanted to install nearly a thousand packages.&lt;&#x2F;p&gt;
&lt;p&gt;I always had a bias towards KDE considering how I disliked it without giving it much of a try.
If it hadn&#x27;t been for the recent changes in the Gnome desktop, I probably would have installed Gnome instead.
But to my surprise, I am really liking the Plasma desktop.&lt;&#x2F;p&gt;
&lt;p&gt;The desktop has a ton of features and is extremely flexible and customizable.
Coming from a patched up minimal system, the full featured desktop feels really cozy and I am loving it.
KDE also has a rich set of applications which are packed with features that fit my needs almost perfectly and also help maintain a coherent desktop.
Almost everything works out of the box and configuring is no difficult than toggling a few options.
The theming seems to be much better than how it was on GTK based desktops.
I am also really impressed with the resource utilization considering how feature rich the desktop is.&lt;&#x2F;p&gt;
&lt;p&gt;The amount of time I spend configuring my system has drastically decreased.
I now have more time on my hands which I have been spending on learning new stuff that are actually worthwhile and important to me than just obsessing over resource usage and package counts.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
