<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Abstract Symphony</title>
  <link href="https://github.com/borkdude/quickblog/atom.xml" rel="self"/>
  <link href="https://github.com/borkdude/quickblog"/>
  <updated>2024-11-26T05:10:48+00:00</updated>
  <id>https://github.com/borkdude/quickblog</id>
  <author>
    <name>Quick Blogger</name>
  </author>
  <entry>
    <id>https://github.com/borkdude/quickblog/notes.html</id>
    <link href="https://github.com/borkdude/quickblog/notes.html"/>
    <title>Notes</title>
    <updated>2024-11-26T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<h2 id="why?">Why?</h2><p>Keeping a list of public notes, that I found interesting or useful in my day to day work.</p><h2 id="content">Content</h2><ul><li><a href='#exposing_local_server_to_internet'>Exposing local server to Internet</a></li></ul><h3 id="exposing&#95;local&#95;server&#95;to&#95;internet">Exposing local server to Internet</h3><p>Cloudflare makes <code>cloudflared</code> available for tunneling purpose.</p><ul><li>Install by <code>sudo pacman -Sy cloudflared</code></li><li>Run to expose a http server running on port 3000 by <code>cloudflared tunnel --url http://localhost:3000</code></li><li>We get a public url like: <code>https://pollution-lmao-occasions-dracony.trycloudflare.com</code></li></ul>]]></content>
  </entry>
  <entry>
    <id>https://github.com/borkdude/quickblog/clj-automation-1.html</id>
    <link href="https://github.com/borkdude/quickblog/clj-automation-1.html"/>
    <title>Clojure Automation #1</title>
    <updated>2024-11-07T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<p>In this post, I share how I automated checking for passport application status using clojure from scratch.</p><h2 id="content">Content</h2><ul><li><a href='#why?'>Why?</a></li><li><a href='#100_ft_view'>100 ft View</a></li><li><a href='#project_setup'>Project Setup</a></li><li><a href='#scraping_information'>Scraping Information</a><ul><li>Figuring out the API</li><li>Parsing Information</li></ul></li><li><a href='#telegram_bot_setup'>Telegram Bot Setup</a><ul><li>Creating the bot and channel</li><li>Sending message</li></ul></li><li><a href='#deployment'>Deployment</a></li><li><a href='#conclusion'>Conclusion</a></li></ul><h2 id="why?">Why?</h2><p>My whole family has applied for new passports, and I want to know the application status. But every time I open the <a href='https://www.passportindia.gov.in/AppOnlineProject/welcomeLink'>website</a>, I feel like giving up on life.</p><p>What could be worse, you say? Well, doing it four times.</p><p>Now, I use <code>Check Application Status</code> option on the homepage. It's an optimization by storing <code>File Numbers</code> and <code>Date of Births</code> in my BitWarden extension. That helps avoid having to fill the Captcha and logging in multiple times. It still hurts, albeit a bit less.</p><p>I abhor the 2 minutes, I spend each time I have to check the status. So, I'll spend next 5 hours setting up an automation script to notify me every 3 hours.</p><h2 id="100&#95;ft&#95;view">100 ft View</h2><p>I will need the following:</p><ul><li>A job running every 3 hours</li><li>A way to fetch passport application status</li><li>A way to notify me</li></ul><p>This is how it's going to work:</p><ul><li>At the onset of each 3rd hour, run a script</li><li>Script will scrape the information from passport status webpage</li><li>Parse out required information from the scraped page</li><li>Send a notification to a chat app (whatsapp or telegram)</li></ul><h2 id="project&#95;setup">Project Setup</h2><p>I start by defining the <code>deps.edn</code> file in the project root. This is the beginning of the project. <code>deps.edn</code> will contain the project dependencies and configuration for build and release.</p><p>Which dependencies will I need?</p><ul><li>To scrape information<ul><li>HTTP Client</li><li>HTML Parser and Selector</li><li>JSON encoder and decoder to convert from clojure data structures to JSON</li></ul></li><li>To send messages to Telegram<ul><li>Again the same dependencies, except HTML Parser</li></ul></li><li>For development a REPL server library</li></ul><p><code>deps.edn</code> file looks like the following:</p><pre><code class="lang-clojure">; deps.edn
{:paths &#91;&quot;src&quot;&#93;                                                     ; Where the source code lives
 :deps {clj-http/clj-http {:mvn/version &quot;3.13.0&quot;}                   ; HTTP Client
        org.clj-commons/hickory {:mvn/version &quot;0.7.5&quot;}              ; HTML Parser and Selector
        cheshire/cheshire {:mvn/version &quot;5.13.0&quot;}}                  ; JSON encoder/decoder
 :aliases {:dev {:extra-paths &#91;&quot;env/dev&quot;&#93;                           ; dev environment config
                 :extra-deps {nrepl/nrepl {:mvn/version &quot;1.3.0&quot;}}   ; nrepl server development
                 :main-opts &#91;&quot;-m&quot; &quot;nrepl.cmdline&quot;&#93;}                 ; Options for main function in `dev` alias
           :prod {:extra-paths &#91;&quot;env/prod&quot;&#93;}                        ; Prod config
           :passport {:exec-fn passport-status/execute!}}}          ; Alias to execute actual flow
</code></pre><p>Now I can start the REPL server by executing the following command in the terminal:<pre><code class="lang-sh">$ clj -M:dev
</code></pre></p><p>As an output of the command, I'll get the <code>nrepl-port</code> that I can use to connect to the REPL server from the editor. I can also use the <code>.nrepl-port</code> file created by the above command.</p><p>I also create the following directory structure:<pre><code class="lang-sh">
➜  automations git:&#40;master&#41; ✗ tree .
.
├── deps.edn
├── env
│   ├── dev
│   │   └── config.clj
│   └── prod
│       └── config.clj
└── src
    ├── passport&#95;status.clj
    └── telegram.clj

4 directories, 5 files
</code></pre></p><ul><li><code>src/passport&#95;status.clj</code> contains all the logic related to passport status fetching</li><li><code>src/telegram.clj</code> contains all the logic to interact with <code>telegram</code></li></ul><h2 id="scraping&#95;information">Scraping Information</h2><p>To find out how to extract passport application information, I manually go through the process.</p><h3 id="figuring&#95;out&#95;the&#95;api">Figuring out the API</h3><ul><li>Go to the <a href='https://passportindia.gov.in'>Passport Website FrontPage</a></li><li>Close the annoying popup.</li><li>Oh, I see a <code>Track Application Status</code> button on the left.</li></ul><p><img style="align-self: center;" src="./assets/clj-automation-1/passport-main-page.png" alt="Last goodbye to Dadaji" height="300" width="300" /></p><ul><li>When I click on it, they ask to provide <code>File Number</code> and <code>Date of Birth</code>, and type of service.</li><li>When I fill that info and click <code>Track Status</code>. I notice the following request being sent in the browser's network tab (Copied in the curl format):</li></ul><pre><code class="lang-sh">curl 'https://passportindia.gov.in/AppOnlineProject/statusTracker/trackStatusInpNew' \
  -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,&#42;/&#42;;q=0.8,application/signed-exchange;v=b3;q=0.7' \
  -H 'Accept-Language: en-US,en;q=0.9,hi-IN;q=0.8,hi;q=0.7' \
  -H 'Cache-Control: max-age=0' \
  -H 'Connection: keep-alive' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'Cookie: JSESSIONID=&lt;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&gt;' \
  -H 'DNT: 1' \
  -H 'Origin: https://passportindia.gov.in' \
  -H 'Referer: https://passportindia.gov.in/AppOnlineProject/statusTracker/trackStatusInpNew' \
  -H 'Sec-Fetch-Dest: document' \
  -H 'Sec-Fetch-Mode: navigate' \
  -H 'Sec-Fetch-Site: same-origin' \
  -H 'Sec-Fetch-User: ?1' \
  -H 'Upgrade-Insecure-Requests: 1' \
  -H 'sec-ch-ua-mobile: ?0' \
  --data-raw 'apptUrl=&amp;apptRedirectFlag=&amp;optStatus=Application&#95;Status&amp;fileNo=&lt;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&gt;&amp;applDob=&lt;DD/MM/YYY&gt;&amp;rtiRefNo=&amp;diplArnNo=&amp;appealNo=&amp;appealDob=&amp;action%3AtrackStatusForFileNoNew=Track+Status'

</code></pre><p>Ahah, can I just send it over again and get the same response? Hell yeah. When I execute the above command on terminal, I get an HTTP response. (Thank God, for no Captcha)</p><p>Perfect. Now, let's also try without all those unnecessary headers and data in request body. Turns out we just need to send the request body:<pre><code class="lang-json">{
  &quot;optStatus&quot;: &quot;Application&#95;Status&quot;,
  &quot;fileNo&quot;: &quot;ABC&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&#42;&quot;,
  &quot;applDob&quot;: &quot;DD/MM/YYYY&quot;,
  &quot;action:trackStatusForFileNoNew&quot;: &quot;Track Status&quot;
}
</code></pre></p><p>That really simplifies the problem.</p><h3 id="parsing&#95;information">Parsing Information</h3><p>Let's start by fetching the response. This code goes in <code>src/passport&#95;status.clj</code>.<pre><code class="lang-clojure">&#40;ns passport-status
  &#40;:require &#91;clj-http.client :as http&#93;&#41;&#41;

;; Data for each passport application
&#40;def details
  &#91;{:file-num &quot;ABC234324234&quot;
    :dob &quot;15/09/1992&quot;}
   {:file-num &quot;ABC234324234&quot;
    :dob &quot;24/11/1987&quot;}
   {:file-num &quot;ABC234324234&quot;
    :dob &quot;10/01/1971&quot;}
   {:file-num &quot;ABC234324234&quot;
    :dob &quot;19/02/2001&quot;}&#93;&#41;

;; URL to fetch passport application status
&#40;def &#94;:private url
  &quot;https://www.passportindia.gov.in/AppOnlineProject/statusTracker/trackStatusInpNew&quot;&#41;

;; Function that sends a POST request to the above URL
;; And fetches the actual HTML response
&#40;defn- fetch-resp &#91;{:keys &#91;file-num dob&#93;}&#93;
  &#40;http/post url {:form-params {:optStatus &quot;Application&#95;Status&quot;
                                :fileNo file-num
                                :applDob dob
                                :action:trackStatusForFileNoNew &quot;Track Status&quot;}}&#41;&#41;
</code></pre></p><p>The above code, should help me fetch the response from the server. Now it's time to parse the response and extract the structured information.</p><p>I am inspecting the web page source in the browser's <code>developer tools</code>. The information is in the following structure:<pre><code class="lang-html">&lt;div class=&quot;hd&#95;blue&quot;&gt;
&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;
        &lt;table&gt;
          &lt;tbody&gt;
            &lt;tr&gt;
              &lt;td&gt; &lt;-- This is what we need
            &lt;tr&gt;
              &lt;td&gt; &lt;-- This is what we need
            &lt;tr&gt;
              &lt;td&gt; &lt;-- This is what we need
</code></pre></p><p>Hickory makes it super easy to select those elements with it's <code>hickory.select</code> functions.</p><pre><code class="lang-clojure">&#40;ns passport-status
  &#40;:require &#91;clj-http.client :as http&#93;
            &#91;hickory.core :as h&#93;
            &#91;hickory.select :as s&#93;&#41;&#41;

;; Omitted code

;; Selecting required data from hickory format
&#40;defn- select-data &#91;hickory&#93;
  &#40;-&gt;&gt; hickory
       &#40;s/select &#40;s/follow-adjacent &#40;s/class &quot;hd&#95;blue&quot;&#41; &#40;s/tag :table&#41;&#41;&#41; ; selects element with class `hd&#95;blue` and moves to the adjacent `table` element.
       first                                                             ; Gets the first such element
       &#40;s/select &#40;s/child &#40;s/tag :table&#41;                                 ; Traverses the heirarchy shown in the previous code snippet
                          &#40;s/tag :tbody&#41;
                          &#40;s/tag :tr&#41;
                          &#40;s/tag :td&#41;
                          &#40;s/tag :table&#41;
                          &#40;s/tag :tbody&#41;
                          &#40;s/tag :tr&#41;
                          &#40;s/tag :td&#41;&#41;&#41;
       &#40;mapcat &#40;fn &#91;td&#93;                                                  ; From here on, we are just extracting the text from the `td`s &#40;Unintended&#41;
                 &#40;:content td&#41;&#41;&#41;
       &#40;partition-all 2&#41;
       &#40;take 7&#41;&#41;&#41;
</code></pre><h2 id="telegram&#95;bot&#95;setup">Telegram bot setup</h2><p>Next step is to setup a bot for telegram, that will send up messages with data extracted in the previous steps. I chose <code>telegram</code> over <code>whatsapp</code> because of the simplicity of using its APIs.</p><h3 id="creating&#95;the&#95;bot&#95;and&#95;channel">Creating the bot and channel</h3><p>I need a bot that authenticates me to use telegram APIs and a channel where the bot will send me messages. I can have the bot send messages to me personally, but I'll have it send to a channel, so that all the members of the channel can see the updates.</p><p>Telegram has a special bot that creates other bots. It's rightly named the <code>@BotFather</code>. Sending a <code>/help</code> message lists all the available commands to interact with it. Most important for us is the <code>/newbot</code> command. This command triggers a flow to create new bot and asks a few questions related to it.</p><ul><li>Name of the new bot</li><li>A globally unique username that ends with <code>bot</code></li></ul><p>Once the bot is created, it sends a welcome message with the bot token and a link to chat with the bot. Bot token will be used to authenticate requests to the telegram API.</p><p>I'll also create a private channel and add the bot to it as an admin.</p><h3 id="sending&#95;message">Sending message</h3><p>Now, that I have the bot token and created the channel. I need the <code>channel&#95;id</code> to send the message to the channel. It's hard to find it on the telegram app or website. The way we'll do it is by using <a href='https://core.telegram.org/bots/api#getupdates'>getUpdates</a> API endpoint. This endpoint returns the data that bot has received using the long polling method. The data contains the <code>channel&#95;id</code> as well.</p><p>First step is to send a message in the channel I created, for the bot to receive it as update. So, I send a <code>hi</code> message in the telegram channel. To get the updates, I'll use the repl:<pre><code class="lang-clojure">&#40;require '&#91;clj-http.client :as http&#93;&#41;
&#40;require '&#91;cheshire.core :as json&#93;&#41;

&#40;def bot-token &quot;&lt;the-bot-token-from-previous-steps&gt;&quot;&#41;

&#40;-&gt; &#40;str &quot;https://api.telegram.org/bot&quot; bot-token &quot;/getUpdates&quot;&#41;
    &#40;http/get {:headers {:content-type &quot;application/json&quot;}}&#41;
    :body
    &#40;json/parse-string&#41;&#41;

; {&quot;ok&quot; true,
;  &quot;result&quot;
;  &#91;{&quot;update&#95;id&quot; 242976779,
;    &quot;channel&#95;post&quot;
;    {&quot;message&#95;id&quot; 8,
;     &quot;sender&#95;chat&quot;
;     {&quot;id&quot; &lt;chat-id&gt;, &quot;title&quot; &quot;Bot Devel&quot;, &quot;type&quot; &quot;channel&quot;},
;     &quot;chat&quot;
;     {&quot;id&quot; &lt;chat-id&gt;, &quot;title&quot; &quot;Bot Devel&quot;, &quot;type&quot; &quot;channel&quot;},
;     &quot;date&quot; 1731041338,
;     &quot;text&quot; &quot;hi&quot;}}&#93;}
</code></pre></p><p>The response is shown above. I get the <code>&lt;chat-id&gt;</code> from there, and save it for later use. Test sending the message by:<pre><code class="lang-clojure">&#40;def chat-id 1234434&#41; ; Replace this with &lt;chat-id&gt;

&#40;-&gt; &#40;str &quot;https://api.telegram.org/bot&quot; bot-token &quot;/sendMessage&quot;&#41;
    &#40;http/post {:headers {:content-type &quot;application/json&quot;}
                :body &#40;json/encode {:chat&#95;id chat-id
                                    :text &quot;Hello&quot;}&#41;}&#41;&#41;
</code></pre></p><p>I get a <code>Hello</code> text on the telegram channel. Ready to assemble the <code>telegram.clj</code> file.</p><pre><code class="lang-clojure">&#40;ns telegram
  &#40;:require &#91;clj-http.client :as http&#93;
            &#91;cheshire.core :as json&#93;
            &#91;config :as cfg&#93;&#41;&#41;

&#40;def &#94;:private bot-url
  &#40;str &quot;https://api.telegram.org/bot&quot; &#40;-&gt; cfg/telegram :bot-token&#41;&#41;&#41;

&#40;defn- telegram-url &#91;method&#93;
  &#40;case method
    :send-message &#40;str bot-url &quot;/sendMessage&quot;&#41;
    :get-updates &#40;str bot-url &quot;/getUpdates&quot;&#41;&#41;&#41;

&#40;defn- channel-id &#91;channel&#93;
  &#40;or &#40;get-in cfg/telegram &#91;:channel-ids channel&#93;&#41;
      &#40;when &#40;= cfg/env :dev&#41;
        &#40;get-in cfg/telegram &#91;:channel-ids :default&#93;&#41;&#41;&#41;&#41;

&#40;defn send-message! &#91;channel message&#93;
  &#40;http/post &#40;telegram-url :send-message&#41;
             {:headers {:content-type &quot;application/json&quot;}
              :body &#40;json/encode {:chat&#95;id &#40;channel-id channel&#41;
                                  :text message
                                  :parse&#95;mode &quot;HTML&quot;}&#41;}&#41;&#41;
</code></pre><p><code>config</code> namespace comes from <code>env/dev/config.clj</code> file. The structure looks something like the following:<pre><code class="lang-clojure">&#40;ns config&#41;

&#40;def telegram
  {:bot-token &quot;&lt;bot-token&gt;&quot;
   :channel-ids {:default &lt;chat-id&gt;}}&#41;

&#40;def env :dev&#41;
</code></pre></p><p><code>env/prod/config.clj</code> will have the same structure with prod related config.</p><p>The final <code>passport-status</code> namespace looks like the following, with some missing pieces and them integrated.<pre><code class="lang-clojure">
&#40;ns passport-status
  &#40;:require
   &#91;clj-http.client :as http&#93;
   &#91;hickory.core :as h&#93;
   &#91;hickory.select :as s&#93;
   &#91;clojure.string :as str&#93;
   &#91;telegram :as tg&#93;&#41;&#41;

&#40;def details
  &#91;{:file-num &quot;ABC234324234&quot;
    :dob &quot;15/09/1992&quot;}
   {:file-num &quot;ABC234324234&quot;
    :dob &quot;24/11/1987&quot;}
   {:file-num &quot;ABC234324234&quot;
    :dob &quot;10/01/1971&quot;}
   {:file-num &quot;ABC234324234&quot;
    :dob &quot;19/02/2001&quot;}&#93;&#41;

&#40;def &#94;:private url
  &quot;https://www.passportindia.gov.in/AppOnlineProject/statusTracker/trackStatusInpNew&quot;&#41;

&#40;defn- fetch-resp &#91;{:keys &#91;file-num dob&#93;}&#93;
  &#40;http/post url {:form-params {:optStatus &quot;Application&#95;Status&quot;
                                :fileNo file-num
                                :applDob dob
                                :action:trackStatusForFileNoNew &quot;Track Status&quot;}}&#41;&#41;

&#40;defn- select-data &#91;hickory&#93;
  &#40;-&gt;&gt; hickory
       &#40;s/select &#40;s/follow-adjacent &#40;s/class &quot;hd&#95;blue&quot;&#41; &#40;s/tag :table&#41;&#41;&#41;
       first
       &#40;s/select &#40;s/child &#40;s/tag :table&#41;
                          &#40;s/tag :tbody&#41;
                          &#40;s/tag :tr&#41;
                          &#40;s/tag :td&#41;
                          &#40;s/tag :table&#41;
                          &#40;s/tag :tbody&#41;
                          &#40;s/tag :tr&#41;
                          &#40;s/tag :td&#41;&#41;&#41;
       &#40;mapcat &#40;fn &#91;td&#93;
                 &#40;:content td&#41;&#41;&#41;
       &#40;partition-all 2&#41;
       &#40;take 7&#41;&#41;&#41;

&#40;defn- format-message &#91;data&#93;
  &#40;-&gt;&gt; data
       &#40;map &#40;fn &#91;&#91;key val&#93;&#93;
              &#40;format &quot;&lt;b&gt;%s&lt;/b&gt;: %s&quot; &#40;str/trim key&#41; &#40;str/trim val&#41;&#41;&#41;&#41;
       &#40;str/join &quot;\r\n&quot;&#41;
       &#40;str &quot;&lt;b&gt;&lt;u&gt;&quot; &#40;java.time.LocalDate/now&#41;
            &quot; &quot; &#40;-&gt; &#40;java.time.LocalTime/now&#41;
                    str
                    &#40;str/split #&quot;\.&quot;&#41;
                    first&#41;
            &quot;&lt;/u&gt;&lt;/b&gt; \r\n&quot;&#41;&#41;&#41;

&#40;defn &#94;:export execute! &#91;&amp; &#95;&#93;
  &#40;-&gt;&gt; details
       &#40;pmap &#40;fn &#91;info&#93;
               &#40;-&gt; &#40;fetch-resp info&#41;
                   &#40;:body&#41;
                   &#40;h/parse&#41;
                   &#40;h/as-hickory&#41;&#41;&#41;&#41;
       &#40;pmap select-data&#41;
       &#40;pmap format-message&#41;
       &#40;pmap #&#40;tg/send-message! :infinite %&#41;&#41;
       &#40;doall&#41;&#41;&#41;
</code></pre></p><p>Understanding the above code is left as an exercise to the reader. From the <code>passport-status</code> ns, I just need to call the <code>telegram/send-message!</code> with the channel and <a href='https://core.telegram.org/bots/api#formatting-options'>formatted message data</a>.</p><h2 id="deployment">Deployment</h2><p>Now, I need a way to setup a job that runs every 3 hours or so. I'll use an EC2 instance on AWS that I already use to host my personal projects.</p><p>First, I copy the project to the EC2 machine. Then, I use systemd timers for job scheduling because of its simple syntax and configuration.</p><p>I create two files for systemd config.</p><ul><li><code>passportstatus.service</code>: keeps the config about what commands to run that will execute the <code>passport-status/execute!</code> function.</li><li><code>passportstatus.timer</code>: keeps the timer configuration, when to run the job and how often.</li></ul><h4 id="passportstatus.service">passportstatus.service</h4><pre><code class="lang-systemd">&#91;Unit&#93;
Description=&quot;Passport Fetch status&quot;

&#91;Service&#93;
WorkingDirectory=/home/user/automation
ExecStart=/bin/bash -c &quot;/usr/local/bin/clojure -X:prod:passport&quot;
</code></pre><h4 id="passportstatus.timer">passportstatus.timer</h4><pre><code class="lang-systemd">&#91;Unit&#93;
Description=&quot;Run a job to fetch passport status.&quot;

&#91;Timer&#93;
OnStartupSec=5seconds
OnBootSec=5min
OnUnitActiveSec=24h
OnCalendar=&#42;-&#42;-&#42; 09/3:00:00 # Runs every three hours starting at 9 AM.
Unit=passportstatus.service

&#91;Install&#93;
WantedBy=multi-user.target
</code></pre><p>Create softlinks to these files in <code>/etc/systemd/system</code> directory. And enable the <code>passportstatus.timer</code> unit by:<pre><code class="lang-sh">$ sudo systemctl enable passportstatus.timer
</code></pre></p><h2 id="conclusion">Conclusion</h2><p>It was a fun little project, that would have taken a few days for me to finish, just a few months back. This exercise boosted my confidence in executing a project from scratch in clojure and getting to deployment. This is just the start of more automations, that'll come in the future.</p><p>Thanks for reading and stay tuned for more.</p>]]></content>
  </entry>
  <entry>
    <id>https://github.com/borkdude/quickblog/aftermath-to-life.html</id>
    <link href="https://github.com/borkdude/quickblog/aftermath-to-life.html"/>
    <title>Aftermath to life</title>
    <updated>2024-08-10T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<p>Never before, this month of August 2024, had I given importance to a soul's departure. The emotion was that of indifference. A straight faced response of "So what". So what if they died. It's the natural order of life. One of the pre-requisites to life, is the fate of an end.</p><p>This was, until I lost someone close. Someone, I loved, respected and adored. My grandfather, one of the rare men that my heart has deep respect for. I never could fathom the pain, his departure broght. Although the pain was extreme, I had experienced it before in a different shade of black. It wasn't that of a death but a loss, nonetheless.</p><p>My grandfather, <strong>Mahipat Singh Patel</strong>, teacher, a man of few words and a strong character, left us in the morning of August 4th, 2024. His departure was painless, yet the journey to that point was filled with suffering. My heart breaks when I think about his last few weeks, yet I am thankful that he didn't suffer more. Dadaji, you are in a better place, I just miss your presence in my life. I already miss, just existing alongside you.</p><p>We spoke rarely, more rare were our meaningful conversations. You didn't express yourself, as much as I would want to understand you. You were quiet and quite consistent. A simple man, who realised the truth of his life. You tried to fulfill our wishes, without having any expectation.</p><p>You sat outside our home and watched people passing by. Some would wish you the time of day, others just pass and still others touch your feet. You would notice them passing by, responding to their wishes in return. After a while, you would return back to your thoughts. I miss these ordinary events of our lives.</p><p>I only have one regret. You never depended on anyone all your life. In the last 3 months, you completely gave off responsibility of your life to us, me in particular. I failed at that. I am sorry. You didn't deserve to go this soon. I miss you</p><p>You did more than your duties. You gave more than you had. You cared more than what was asked of you. I promise to follow your footsteps.</p><p><img src="./assets/dadaji.jpeg" alt="Last goodbye to Dadaji" width="100%" /></p><blockquote><p> ॐ शांति! ॐ शांति! ॐ शांति! </p></blockquote>]]></content>
  </entry>
  <entry>
    <id>https://github.com/borkdude/quickblog/unfinished-suffering.html</id>
    <link href="https://github.com/borkdude/quickblog/unfinished-suffering.html"/>
    <title>[Unfinished] Suffering</title>
    <updated>2024-06-08T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<p>We keep romanticizing suffering. That's all okay. But, if that's all we do about our suffering, we are going in the wrong direction.</p><p>Suffering is necessary to feel the joy from its absence. But what if suffering itself becomes joyful. We lose the incentive to overcome it.</p><p>We don't think about the end of the dark tunnel, the dawn after a dark stormy night.</p><p>We are content with the suffering itself because it's common among our peers. The good old sense of belonging wins over the will to rid ourselves of the pain.</p><p>That's why I prefer solitude. There's no other that I subconsciously mimic. There's no one I secretly love, envy or hate. I am an open book, when alone, albeit a messed up one.</p><p>A whole lot of us dislike change, including me. Mainly the ones that come unexpectedly, that we have no idea about.</p><p>eof</p>]]></content>
  </entry>
  <entry>
    <id>https://github.com/borkdude/quickblog/neovim-clojure-pde-2.html</id>
    <link href="https://github.com/borkdude/quickblog/neovim-clojure-pde-2.html"/>
    <title>Neovim as the Clojure PDE - II</title>
    <updated>2024-02-29T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<p><a href='/neovim-clojure-pde-1'>Click here</a> if you missed the first part.</p><p>Welcome back, folks. It's been a while since the first post of this series came out.</p><p>Today's agenda is to install some basic plugins and configure them. While doing that, we'll learn some fennel and have our own config in parallel.</p><h3 id="why&#95;plugins?">Why Plugins?</h3><p>Plugins enhance the raw editor by adding features that the raw Neovim doesn't provide out of the box. In VSCode world, we call them extensions.</p><p>We need a plugin that makes it easy for us to install and use other plugins. These are called <code>Plugin Managers</code>. There are many of them in Neovim/Vim world like <a href='https://github.com/wbthomason/packer.nvim'>Packer</a>, <a href='https://github.com/junegunn/vim-plug'>Vim Plug</a>, <a href='https://github.com/folke/lazy.nvim'>Lazy</a> et al. For the purpose of this post and because it's the new shiny thing, we will use <strong>Lazy</strong>.</p><h3 id="plugins.fnl">plugins.fnl</h3><p>Just to recap, after our <a href='/neovim-clojure-pde-1'>last post</a>, the config directory looks like the following<pre><code class="lang-sh">$ tree -L 3 &#126;/.config/nvim
├── fnl
│   └── user
│       └── core.fnl
├── init.lua
└── lua
    └── user
        └── core.lua
</code></pre></p><p>We are going to add the following plugins now:</p><ul><li><a href='https://github.com/neovim/nvim-lspconfig'>lsp-config</a> - Language Server Protocol client for neovim</li><li><a href='https://github.com/nvim-treesitter/nvim-treesitter'>nvim-treesitter</a> - Allows to parse code into abstract syntax tree and use that in better syntax highlighting, code traversal et al.</li><li><a href='https://github.com/hrsh7th/nvim-cmp'>nvim-cmp</a> - Autocompletion for neovim</li><li><a href='https://github.com/nvim-telescope/telescope.nvim'>telescope.nvim</a> - Fuzzy finder for neovim</li><li><a href='https://github.com/Olical/conjure'>conjure</a> - Allows you to hook into REPl servers</li><li><a href='https://github.com/Olical/conjure'>nvim-paredit</a> - S-expression motions made easy</li><li><a href='https://github.com/HiPhish/rainbow-delimiters.nvim'>rainbow-delimiters</a> - Colorful parenthesis</li><li><a href='https://github.com/windwp/nvim-autopairs'>autopairs</a> - Allows working with pair characters easy</li></ul><p>We'll put all the plugin installation related configuration in the <code>plugins.fnl</code> file. Let's start with installing <code>Lazy</code>, a lua based plugin manager for neovim.</p><pre><code class="lang-clojure">;; fnl/user/plugins.fnl

;; Path to directory where lazy itself will be installed.
;; `&#40;vim.fn.stdpath :data&#41;` returns standard directory where nvim's data will be stored.
;; `..` is string concatenation in fnl.
&#40;local lazy-path &#40;.. &#40;vim.fn.stdpath :data&#41;
                     :/lazy/lazy.nvim&#41;&#41;

;; `&#40;vim.uv.fs&#95;stat lazy-path&#41;` returns nil if the `lazy-path` doesn't exist.
;; If the directory exists, it returns info about the directory.
&#40;local lazy-installed? &#40;vim.uv.fs&#95;stat lazy-path&#41;&#41;

;; We'll store plugins we need to install in this variable.
&#40;local to-install &#91;&#93;&#41;

;; `setup` installs lazy plugin manager if not already installed, and also downloads the plugins added to `to-install`.
;; This is an exported function that is called from the parent module.
&#40;fn setup &#91;&#93;
  &#40;when &#40;not lazy-installed?&#41;
    &#40;vim.fn.system &#91;:git
                    :clone
                    &quot;--filter=blob:none&quot;
                    &quot;https://github.com/folke/lazy.nvim.git&quot;
                    :--branch=stable
                    lazy-path&#93;&#41;&#41;
  &#40;vim.opt.rtp:prepend lazy-path&#41;
  &#40;let &#91;lazy &#40;autoload :lazy&#41;&#93;
    &#40;lazy.setup plugins-to-install&#41;&#41;&#41;

;; Exporting the setup function.
{: setup}
</code></pre><p>Let's import <code>plugins.fnl</code> in <code>fnl/user/core.fnl</code> and call the <code>setup</code> function.</p><pre><code class="lang-clojure">;; fnl/user/core.fnl
&#40;local plugins &#40;require :user.plugins&#41;&#41;

&#40;fn setup &#91;&#93;
  &#40;plugins.setup&#41;&#41;

{: setup}
</code></pre><p>In our <code>init.lua</code>, we need to call <code>setup</code> function of our <code>core</code> module.</p><pre><code class="lang-lua">-- init.lua
-- ...

require&#40;'user.core'&#41;.setup&#40;&#41;
</code></pre><p>Now, when we restart the neovim, the lazy plugin should be installed.</p><p>It is time for us to add the plugins we listed above.</p><pre><code class="lang-clojure">;; fnl/user/plugins.fnl

;; ...
&#40;local to-install
  &#91;{1 :neovim/nvim-lspconfig
    :dependencies &#91;{1 :williamboman/mason.nvim :config true} ;; Mason handles lsp servers for us and others
                   :williamboman/mason-lspconfig.nvim ;; default lspconfigs for language servers
                   {1 :echasnovski/mini.nvim ;; For lsp progress notification
                    :version false
                    :config &#40;fn &#91;&#93; 
                              &#40;let &#91;mnotify &#40;require :mini.notify&#41;&#93;
                                &#40;mnotify.setup {:lsp&#95;progress {:enable true
                                                               :duration&#95;last 2000}}&#41;&#41;&#41;}
                   :folke/neodev.nvim&#93;} ;; neodev for lua neovim dev support.

   ;; Autocompletion   
   {1 :hrsh7th/nvim-cmp
    :dependencies &#91;:hrsh7th/cmp-nvim-lsp&#93;}

   ;; Fuzzy Finder &#40;files, lsp, etc&#41;
   {1 :nvim-telescope/telescope.nvim
    :version &quot;&#42;&quot;
    :dependencies {1 :nvim-lua/plenary.nvim}}
   ;; Fuzzy Finder Algorithm which requires local dependencies to be built.
   ;; Only load if `make` is available. Make sure you have the system
   ;; requirements installed.
   {1 :nvim-telescope/telescope-fzf-native.nvim
    ;; NOTE: If you are having trouble with this installation,
    ;; refer to the README for telescope-fzf-native for more instructions.
    :build :make
    :cond &#40;fn &#91;&#93; &#40;= &#40;vim.fn.executable :make&#41; 1&#41;&#41;}

   ;; Treesitter
   {1 :nvim-treesitter/nvim-treesitter
    :build &quot;:TSUpdate&quot;}
  
   ;; Conjure and related plugins
   :Olical/conjure
   :PaterJason/cmp-conjure ;; autocomplete using conjure
   :tpope/vim-dispatch
   :clojure-vim/vim-jack-in
   :radenling/vim-dispatch-neovim
   :tpope/vim-surround

   ;; Paredit
   {1 :julienvincent/nvim-paredit
    :config &#40;fn &#91;&#93;
              &#40;let &#91;paredit &#40;require :nvim-paredit&#41;&#93;
                &#40;paredit.setup {:indent {:enabled true}}&#41;&#41;&#41;}
   ;; Paredit plugin for fennel
   {1 :julienvincent/nvim-paredit-fennel
    :dependencies &#91;:julienvincent/nvim-paredit&#93;
    :ft &#91;:fennel&#93;
    :config &#40;fn &#91;&#93;
              &#40;let &#91;fnl-paredit &#40;require :nvim-paredit-fennel&#41;&#93;
                &#40;fnl-paredit.setup&#41;&#41;&#41;}

   ;; Rainbow parens
   :HiPhish/rainbow-delimiters.nvim

   ;; Autopairs
   {1 :windwp/nvim-autopairs
    :event :InsertEnter
    :opts {:enable&#95;check&#95;bracket&#95;line false}}&#93;&#41;
</code></pre><p>Restart neovim, and we see Lazy UI installing the plugins added above.</p><h3 id="common&#95;configuration">Common configuration</h3><p>Let's configure Neovim to solve some day-to-day pains eg. enabling relative line numbering, auto syncing system and neovim clipboards, some useful keymaps etc.</p><p>We'll create a <code>general.fnl</code> where we put all of this.<pre><code class="lang-clojure">;; fnl/user/config/general.fnl

&#40;fn setup &#91;&#93;
  ;; disables highlighting all the search results in the doc
  &#40;set vim.o.hlsearch false&#41;

  ;; line numbering
  &#40;set vim.wo.number true&#41;
  &#40;set vim.wo.relativenumber true&#41;

  ;; disable mouse
  &#40;set vim.o.mouse &quot;&quot;&#41;

  ;; clipboard is system clipboard
  &#40;set vim.o.clipboard :unnamedplus&#41;

  ;; Some other useful opts. `:help &lt;keyword&gt;` for the help.
  ;; For example: `:help breakindent` will open up vimdocs about `vim.o.breakindent` option.
  &#40;set vim.o.breakindent true&#41;
  &#40;set vim.o.undofile true&#41;
  &#40;set vim.o.ignorecase true&#41;
  &#40;set vim.o.smartcase true&#41;
  &#40;set vim.wo.signcolumn :yes&#41;
  &#40;set vim.o.updatetime 250&#41;
  &#40;set vim.o.timeout true&#41;
  &#40;set vim.o.timeoutlen 300&#41;
  &#40;set vim.o.completeopt &quot;menuone,noselect&quot;&#41;
  &#40;set vim.o.termguicolors true&#41;
  &#40;set vim.o.cursorline true&#41;

  ;; Keymaps
  &#40;vim.keymap.set &#91;:n :v&#93; :&lt;Space&gt; :&lt;Nop&gt; {:silent true}&#41;

  ;; To deal with word wrap
  &#40;vim.keymap.set :n :k &quot;v:count == 0 ? 'gk' : 'k'&quot; {:expr true :silent true}&#41;
  &#40;vim.keymap.set :n :j &quot;v:count == 0 ? 'gj' : 'j'&quot; {:expr true :silent true}&#41;

  ;; Tabs &#40;Optional and unrelated to this tutorial, but helpful in handling tab movement&#41;
  &#40;vim.keymap.set &#91;:n :v :i :x&#93; :&lt;C-h&gt; #&#40;vim.api.nvim&#95;command :tabprevious&#41;
                  {:silent true}&#41;
  &#40;vim.keymap.set &#91;:n :v :i :x&#93; :&lt;C-l&gt; #&#40;vim.api.nvim&#95;command :tabnext&#41;
                  {:silent true}&#41;
  &#40;vim.keymap.set &#91;:n :v :i :x&#93; :&lt;C-n&gt; #&#40;vim.api.nvim&#95;command :tabnew&#41;
                  {:silent true}&#41;&#41;

;; exports an empty map
{: setup}
</code></pre></p><h3 id="plugin&#95;configurations">Plugin Configurations</h3><p>It is time for us to configure each of these plugins to cater to our needs. These needs are plugin specific. We may want to add keymaps for commands we use often or tell Mason to make sure that particular LSP is always installed, or anything else that the plugin supports. These settings and configs can often be found on the plugin's github repository and neovim's documentation.</p><h4 id="lsp&#95;configuration">LSP configuration</h4><pre><code class="lang-clojure">;; fnl/user/config/lsp.fnl

&#40;local nfnl-c           &#40;require :nfnl.core&#41;&#41;
&#40;local ts-builtin       &#40;require :telescope.builtin&#41;&#41;
&#40;local cmp-nvim-lsp     &#40;require :cmp&#95;nvim&#95;lsp&#41;&#41;
&#40;local mason-lspconfig  &#40;require :mason-lspconfig&#41;&#41;
&#40;local lspconfig        &#40;require :lspconfig&#41;&#41;
&#40;local neodev           &#40;require :neodev&#41;&#41;

;; This function will be executed when neovim attaches to an LSP server.
;; This basically sets up some widely used keymaps to interact with LSP servers.
&#40;fn on-attach &#91;&#95; bufnr&#93;
  &#40;let &#91;nmap &#40;fn &#91;keys func desc&#93;
               &#40;vim.keymap.set :n keys func
                               {:buffer bufnr 
                                :desc   &#40;.. &quot;LSP: &quot; desc&#41;}&#41;&#41;
        nvxmap &#40;fn &#91;keys func desc&#93;
                 &#40;vim.keymap.set &#91;:n :v :x&#93; keys func
                                 {:buffer bufnr :desc &#40;.. &quot;LSP: &quot; desc&#41;}&#41;&#41;&#93;
    &#40;nmap :&lt;leader&gt;rn vim.lsp.buf.rename &quot;&#91;R&#93;e&#91;n&#93;ame&quot;&#41;
    &#40;nmap :&lt;leader&gt;ca vim.lsp.buf.code&#95;action &quot;&#91;C&#93;ode &#91;A&#93;ction&quot;&#41;
    &#40;nmap :gd vim.lsp.buf.definition &quot;&#91;G&#93;oto &#91;D&#93;efinition&quot;&#41;
    &#40;nmap :gr &#40;fn &#91;&#93; &#40;ts-builtin.lsp&#95;references {:fname&#95;width 60}&#41;&#41;
          &quot;&#91;G&#93;oto &#91;R&#93;eferences&quot;&#41;
    &#40;nmap :gI vim.lsp.buf.implementation &quot;&#91;G&#93;oto &#91;I&#93;mplementation&quot;&#41;
    &#40;nmap :&lt;leader&gt;D vim.lsp.buf.type&#95;definition &quot;Type &#91;D&#93;efinition&quot;&#41;
    &#40;nmap :&lt;leader&gt;ds ts-builtin.lsp&#95;document&#95;symbols &quot;&#91;D&#93;ocument &#91;S&#93;ymbols&quot;&#41;
    &#40;nmap :&lt;leader&gt;ws
          &#40;fn &#91;&#93; &#40;ts-builtin.lsp&#95;dynamic&#95;workspace&#95;symbols {:fname&#95;width 60}&#41;&#41;
          &quot;&#91;W&#93;orkspace &#91;S&#93;ymbols&quot;&#41;
    &#40;nmap :K vim.lsp.buf.hover &quot;Hover Documentation&quot;&#41;
    &#40;nmap :&lt;C-k&gt; vim.lsp.buf.signature&#95;help &quot;Signature Documentation&quot;&#41;
    &#40;nmap :gD vim.lsp.buf.declaration &quot;&#91;G&#93;oto &#91;D&#93;eclaration&quot;&#41;
    &#40;nmap :&lt;leader&gt;wa vim.lsp.buf.add&#95;workspace&#95;folder
          &quot;&#91;W&#93;orkspace &#91;A&#93;dd folder&quot;&#41;
    &#40;nmap :&lt;leader&gt;wr vim.lsp.buf.remove&#95;workspace&#95;folder
          &quot;&#91;W&#93;orkspace &#91;R&#93;emove folder&quot;&#41;
    &#40;nmap :&lt;leader&gt;wl
          &#40;fn &#91;&#93;
            &#40;nfnl-c.println &#40;vim.inspect &#40;vim.lsp.buf.list&#95;workspace&#95;folders&#41;&#41;&#41;&#41;
          &quot;&#91;W&#93;orkspace &#91;L&#93;ist folders&quot;&#41;
    &#40;nvxmap :&lt;leader&gt;fmt vim.lsp.buf.format
            &quot;&#91;F&#93;or&#91;m&#93;a&#91;t&#93; the current buffer or range&quot;&#41;&#41;&#41;

;; This binding keeps a map from lsp server name and its settings.
&#40;local servers
       {:clojure&#95;lsp            {:paths-ignore-regex :conjure-log-&#42;.cljc}
        :lua&#95;ls                 {:Lua {:workspace {:checkThirdParty false}
                                       :telemetry {:enable false}}}
        :fennel&#95;language&#95;server {:fennel {:diagnostics  {:globals &#91;:vim&#93;}
                                          :workspace    {:library &#40;vim.api.nvim&#95;list&#95;runtime&#95;paths&#41;}}}}&#41;

&#40;local capabilities
       &#40;cmp-nvim-lsp.default&#95;capabilities &#40;vim.lsp.protocol.make&#95;client&#95;capabilities&#41;&#41;&#41;

&#40;fn setup &#91;&#93;
  &#40;neodev.setup&#41;
  &#40;mason-lspconfig.setup {:ensure&#95;installed &#40;nfnl-c.keys servers&#41;}&#41;
  &#40;mason-lspconfig.setup&#95;handlers &#91;&#40;fn &#91;server-name&#93;
                                     &#40;&#40;. &#40;. lspconfig server-name&#41; :setup&#41; 
                                        {: capabilities
                                         :on&#95;attach on-attach
                                         :settings &#40;. servers server-name&#41;

                                         ;; This is required because clojure-lsp doesn't send LSP progress messages if the `workDoneToken` is not sent from client. It is an arbitrary string.
                                         :before&#95;init
                                         &#40;fn &#91;params &#95;&#93;
                                           &#40;set params.workDoneToken 
                                                &quot;work-done-token&quot;&#41;&#41;}&#41;&#41;&#93;&#41;&#41;

{: setup}
</code></pre><h4 id="autocompletion&#95;configuration">Autocompletion configuration</h4><pre><code class="lang-clojure">;; fnl/user/config/cmp.fnl
&#40;local cmp &#40;require :cmp&#41;&#41;

&#40;fn setup &#91;&#93;
  &#40;cmp.setup {:mapping &#40;cmp.mapping.preset.insert 
                         {:&lt;C-d&gt;     &#40;cmp.mapping.scroll&#95;docs -4&#41;
                          :&lt;C-f&gt;     &#40;cmp.mapping.scroll&#95;docs 4&#41;
                          :&lt;C-Space&gt; &#40;cmp.mapping.complete {}&#41;
                          :&lt;CR&gt;      &#40;cmp.mapping.confirm {:behavior cmp.ConfirmBehavior.Replace
                                                           :select false}&#41;
                          :&lt;Tab&gt;     &#40;cmp.mapping &#40;fn &#91;fallback&#93;
                                                    &#40;if
                                                      &#40;cmp.visible&#41;
                                                      &#40;cmp.select&#95;next&#95;item&#41;

                                                      ;; else
                                                      &#40;fallback&#41;&#41;&#41;&#41;
                          :&lt;S-Tab&gt;   &#40;cmp.mapping &#40;fn &#91;fallback&#93;
                                                    &#40;if 
                                                      &#40;cmp.visible&#41;
                                                      &#40;cmp.select&#95;prev&#95;item&#41;
                                                      
                                                      ;; else
                                                      &#40;fallback&#41;&#41;&#41;&#41;}                        
                         &#91;:i :s&#93;&#41;
              :sources &#91;{:name :conjure}
                        {:name :nvim&#95;lsp}&#93;}&#41;&#41;

{: setup}
</code></pre><h4 id="paredit&#95;configuration">Paredit configuration</h4><pre><code class="lang-clojure">;; fnl/user/config/paredit.fnl

&#40;local paredit      &#40;require :nvim-paredit&#41;&#41;
&#40;local paredit-fnl  &#40;require :nvim-paredit-fennel&#41;&#41;

&#40;fn setup &#91;&#93;
  &#40;paredit.setup {:indent {:enabled true}}&#41;
  &#40;paredit-fnl.setup&#41;&#41;

{: setup}
</code></pre><h4 id="rainbow&#95;delimiters&#95;configuration">Rainbow delimiters configuration</h4><pre><code class="lang-clojure">;; fnl/user/config/rainbow.fnl

&#40;local rainbow-delimiters &#40;require :rainbow-delimiters&#41;&#41;

&#40;fn setup &#91;&#93;
  &#40;set vim.g.rainbow&#95;delimiters
       {:strategy {&quot;&quot; rainbow-delimiters.strategy.local}
        :query    {&quot;&quot; :rainbow-delimiters}}&#41;&#41;

{: setup}
</code></pre><h4 id="telescope&#95;configuration">Telescope configuration</h4><pre><code class="lang-clojure">;; fnl/user/config/telescope.fnl

&#40;local telescope  &#40;require :telescope&#41;&#41;
&#40;local builtin    &#40;require :telescope.builtin&#41;&#41;
&#40;local themes     &#40;require :telescope.themes&#41;&#41;

&#40;fn setup &#91;&#93;
  &#40;telescope.setup {:defaults {:mappings {:i {:&lt;C-u&gt; false :&lt;C-d&gt; false}}}}&#41;
  &#40;telescope.load&#95;extension :fzf&#41;

  ;; keymaps
  &#40;vim.keymap.set :n :&lt;leader&gt;? builtin.oldfiles
                  {:desc &quot;&#91;?&#93; Find recently opened files&quot;}&#41;
  &#40;vim.keymap.set :n :&lt;leader&gt;fb builtin.buffers {:desc &quot;&#91;F&#93;ind open &#91;B&#93;uffers&quot;}&#41;
  &#40;vim.keymap.set :n :&lt;leader&gt;/
                  &#40;fn &#91;&#93;
                    &#40;builtin.current&#95;buffer&#95;fuzzy&#95;find &#40;themes.get&#95;dropdown {:winblend 10
                                                                             :previewer false}&#41;&#41;&#41;
                  {:desc &quot;&#91;/&#93; Fuzzily search in current buffer&quot;}&#41;
  &#40;vim.keymap.set :n :&lt;leader&gt;ff builtin.find&#95;files {:desc &quot;&#91;F&#93;ind &#91;F&#93;iles&quot;}&#41;
  &#40;vim.keymap.set :n :&lt;leader&gt;fh builtin.help&#95;tags {:desc &quot;&#91;F&#93;ind &#91;H&#93;elp&quot;}&#41;
  &#40;vim.keymap.set :n :&lt;leader&gt;fw builtin.grep&#95;string
                  {:desc &quot;&#91;F&#93;ind current &#91;W&#93;ord&quot;}&#41;
  &#40;vim.keymap.set :n :&lt;leader&gt;fg builtin.live&#95;grep {:desc &quot;&#91;F&#93;ind by &#91;G&#93;rep&quot;}&#41;
  &#40;vim.keymap.set :n :&lt;leader&gt;fd builtin.diagnostics
                  {:desc &quot;&#91;F&#93;ind &#91;D&#93;iagnostics&quot;}&#41;
  &#40;vim.keymap.set :n :&lt;leader&gt;fcf
                  &#40;fn &#91;&#93; &#40;builtin.find&#95;files {:cwd &#40;vim.fn.stdpath :config&#41;}&#41;&#41;
                  {:desc &quot;&#91;F&#93;ind &#91;C&#93;onfing &#91;F&#93;iles&quot;}&#41;
  &#40;vim.keymap.set :n :&lt;leader&gt;fch builtin.command&#95;history
                  {:desc &quot;&#91;F&#93;ind in &#91;C&#93;ommands &#91;H&#93;istory&quot;}&#41;
  &#40;vim.keymap.set :n :&lt;leader&gt;fm builtin.marks {:desc &quot;&#91;F&#93;ind in &#91;M&#93;arks&quot;}&#41;&#41;

{: setup}
</code></pre><h4 id="treesitter&#95;configuration">Treesitter configuration</h4><pre><code class="lang-clojure">;; fnl/user/config/treesitter.fnl

&#40;local configs &#40;require :nvim-treesitter.configs&#41;&#41;

&#40;local ensure-installed &#91;:clojure :lua :vimdoc :vim :fennel&#93;&#41;

&#40;fn setup &#91;&#93;
  &#40;configs.setup {:ensure&#95;installed       ensure-installed
                  ;; Add languages to be installed here that you want installed for treesitter
                  :auto&#95;install           true
                  :highlight              {:enable true}
                  :indent                 {:enable  false}}&#41;&#41;

{: setup}
</code></pre><h3 id="gluing&#95;everything&#95;together">Gluing everything together</h3><p>Let's actually call <code>setup</code> methods of all these modules in our <code>core.fnl</code>.<pre><code class="lang-clojure">;; fnl/user/core.fnl
&#40;local plugins          &#40;require :user.plugins&#41;&#41;
&#40;local u-general        &#40;require :user.config.general&#41;&#41;
&#40;local u-telescope      &#40;require :user.config.telescope&#41;&#41;
&#40;local u-treesitter     &#40;require :user.config.treesitter&#41;&#41;
&#40;local u-lsp            &#40;require :user.config.lsp&#41;&#41;
&#40;local u-cmp            &#40;require :user.config.cmp&#41;&#41;
&#40;local u-paredit        &#40;require :user.config.paredit&#41;&#41;
&#40;local u-rainbow        &#40;require :user.config.rainbow&#41;&#41;

&#40;fn setup &#91;&#93;
  &#40;plugins.setup&#41;
  &#40;u-general.setup&#41;
  &#40;u-telescope.setup&#41;
  &#40;u-treesitter.setup&#41;
  &#40;u-lsp.setup&#41;
  &#40;u-cmp.setup&#41;
  &#40;u-paredit.setup&#41;
  &#40;u-rainbow.setup&#41;&#41;

{: setup}
</code></pre></p><p>And that should be it. I know, this blog is long and contains a lot of code. Most of it should be self explanatory once you get familiar with the fennel syntax and vim docs.</p><p>The configuration is inspired by <a href='https://github.com/nvim-lua/kickstart.nvim'>kickstart.nvim</a>. More extensive configuration can be found on my <a href='https://github.com/Samy-33/dotfiles/tree/master/dotcom/.config/nvim'>dotfiles</a>.</p><blockquote><p> If there's a bug somewhere in here or you have a doubt, feel free to open an issue <a href='https://github.com/Samy-33/blog/issues'>here</a>. </p></blockquote>]]></content>
  </entry>
  <entry>
    <id>https://github.com/borkdude/quickblog/neovim-clojure-pde-1.html</id>
    <link href="https://github.com/borkdude/quickblog/neovim-clojure-pde-1.html"/>
    <title>Neovim as the Clojure PDE - I</title>
    <updated>2024-01-26T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<p>Hello, fellow text editor enthusiasts. Welcome to <em>Abstract Symphony</em>, meaning of which, nobody knows and has nothing to do with the post's agenda.</p><p>You may gently ask for the meaning of PDE, what the fuck does that mean?</p><p>Well, you don't have to be so rude. It's an abbreviation for <em>Personalized Development Environment</em>, coined by <a href='https://twitter.com/teej_dv'>TJ</a>, Neovim's marketing head.</p><h3 id="why&#95;neovim?">Why Neovim?</h3><p>You know, same old bullshit like speed, muscle memory, extension of your body/mind, spiritualism, nirvana, moksha and so on. Nothing serious. Jokes aside, the real reason was seeing how fast <a href='https://twitter.com/ThePrimeagen'>Prime</a> was, in <a href='https://youtu.be/SGBuBSXdtLY?si=0HJtUqZEIT3B3izX&t=69'>giving up</a> on clojure.</p><h3 id="why&#95;clojure?">Why Clojure?</h3><p>Because I love my high functioning parens. I can lisp down, 100s of reasons why, but that's beyond the scope. This emotion is hosted on a solid and reliable foundation. I am sure, a dynamic and immutable relationship is what keeps us tied together so strongly. Please forgive me, for my love for punning (nil?). <a href='https://clojure.org/about/rationale'>IYKYK</a>.</p><h3 id="init.lua">init.lua</h3><p>Neovim is a professional's tool. You need to deserve it, earn its respect, you know? Like Thor and his hammer.</p><p>Or you can just <a href='https://github.com/neovim/neovim/blob/master/INSTALL.md'>install</a> neovim and get started. At first sight, it doesn't look like anything more than a trap that you can never get out of (trust me you can :q!). And no, that was not my failed attempt at putting an emojee.</p><p>Create an <code>init.lua</code> file in <code>$HOME/.config/nvim</code> directory. This will be the entrypoint for your neovim configuration. It's similar to the <code>main</code> method in a java/c/cpp projects, an entry-point for the program to run.</p><p>Go ahead and add a <code>print&#40;&quot;Hello, world!&quot;&#41;</code> to the file. Now, when you run <code>nvim</code>, you should see <code>Hello, world!</code> at the bottom-left of your screen. Congratulations, for your first configured neovim instance.</p><h3 id="leader&#95;and&#95;localleader">Leader and LocalLeader</h3><p>Just like how each country needs good leaders to function properly, neovim is no different. You should define a leader and localleader according to your convenience. People usually choose <code>&lt;Space&gt;</code> or <code>&quot;,&quot;</code> as their leader. I'll go ahead with <code>&quot;,&quot;</code> as that is what I am used to.</p><p>But wait, what is the purpose of the leaders, you ask? Well, the main reason is the <code>&quot;plugins&quot;</code>. Plugin writers are not aware of how you have configured your editor. They can't arbitrarily setup keybindings in their plugins, as they may conflict with your bindings.</p><p>Neovim API exposes options via <code>vim.g</code>, <code>vim.o</code>, <code>vim.opt</code>, <code>vim.bo</code> and <code>vim.wo</code>. We can control behaviour of Neovim by setting these options. <code>vim.g</code> is used for <code>global</code> scoped options. We can read more about them in Neovim's help text using <code>:help vim.g</code> or <code>:help vim.o</code> etc.</p><p>Let's set our globally scoped leader and localleader.</p><pre><code class="lang-lua">-- init.lua
vim.g.mapleader = ','
vim.g.maplocalleader = ','
</code></pre><p><strong>NSFW</strong>: <a href='https://learnvimscriptthehardway.stevelosh.com/chapters/06.html'>This article</a> goes deeper into how leader/localleader is helpful.</p><h3 id="we'll&#95;use&#95;fennel&#95;as&#95;our&#95;config&#95;language">We'll use Fennel as our config language</h3><p>Although, Neovim officially supports lua as its configuration language, we will use <a href='https://fennel-lang.org/'>Fennel</a>. Because, we love our parens. And also, we like to struggle and learn.</p><p>Fennel transpiles into lua, so we need a Neovim plugin to transpile our fennel files into lua. Olical's <a href='https://github.com/Olical/nfnl'>nfnl</a> does exactly that. We will update our <code>init.lua</code> to download <code>nfnl</code>. We will also add nfnl's path to neovim's runtime path, so that it can find <code>nfnl</code> plugin.</p><pre><code class="lang-lua">-- init.lua
-- ...

local nfnl&#95;path = vim.fn.stdpath 'data' .. '/lazy/nfnl'

if not vim.uv.fs&#95;stat&#40;nfnl&#95;path&#41; then
  print&#40;&quot;Could not find nfnl, cloning new copy to&quot;, nfnl&#95;path&#41;
  vim.fn.system&#40;{'git', 'clone', 'https://github.com/Olical/nfnl', nfnl&#95;path}&#41;
end

vim.opt.rtp:prepend&#40;nfnl&#95;path&#41;

require&#40;'nfnl'&#41;.setup&#40;&#41;
</code></pre><p>We define a local variable <code>nfnl&#95;path</code>. It holds the directory path where we will download <code>nfnl</code>.</p><ul><li><strong>vim.fn.stdpath 'data'</strong> returns neovim's <code>data</code> directory.<blockquote><p> <code>:help stdpath</code> to know more about standard paths. </li><li>"<strong>..</strong>" is lua's string concatenation operator. So essentially, we get neovim's standard data path and append <code>/lazy/nfnl</code> to it. We'll store <code>nfnl</code> here.</li><li><strong>vim.uv.fs&#95;stat(nfnl_path)</strong> returns information about file/directory. If the file doesn't exist, it returns nil.</li><li><strong>vim.fn.system</strong> allows us to execute shell commands. We can also use <code>vim.system&#40;...&#41;</code> instead. They are exactly the same.</li><li>We ran <code>git clone</code> to download nfnl from its official github repository.</li><li>Then we added <code>nfnl&#95;path</code> to the neovim's runtime path.</li><li>At last, we require the <code>nfnl</code> module and call <code>setup</code> on it.</li></ul></p></blockquote><p>Let us create a directory to store our fennel config files. And also a <code>core.fnl</code> file inside that directory.</p><pre><code class="lang-bash"># Creates the directory
mkdir -p $HOME/.config/nvim/fnl/user

# Creates the file
touch $HOME/.config/nvim/fnl/user/core.fnl
</code></pre><p>Let's add a simple <code>Hello, world!</code> print form in our <code>core.fnl</code>.<pre><code class="lang-clojure">;; fnl/user/core.fnl
&#40;println &quot;Hello, world! This is fennel config!&quot;&#41;
</code></pre></p><p>When we restart our neovim instance, nothing happens. We should have seen a <i>hello world</i> message. We're missing a key <code>nfnl</code> config.</p><p><code>nfnl</code> looks for a <code>.nfnl.fnl</code> file in a directory for configuration about which files to compile and how. Create a <code>.nfnl.fnl</code> file with empty config in <code>$HOME/.config/nvim</code> directory.<pre><code class="lang-bash">echo {} &gt; $HOME/.config/nvim/.nfnl.fnl
</code></pre></p><p>When we restart our instance again, we still don't see anything printed. Well, there are a couple of things pending.</p><ul><li>Fennel files haven't been transpiled yet. <code>nfnl</code> transpiles <code>fnl</code> files on save by default.</li><li>In our <code>init.lua</code>, we haven't <code>require</code>d the <code>user.core</code>, so nvim doesn't know about it just yet.</li></ul><p>On opening <code>core.fnl</code>, a prompt says that <code>.nfnl.fnl</code> is not trusted. Press <code>a</code> to allow (mark it as trusted). This is because <code>nfnl</code> by default won't compile files in a directory unless we specifically allow it to. Once we allow and save the <code>core.fnl</code> file, by using <code>:write</code> command, a new file <code>lua/user/core.lua</code> gets generated with transpiled lua code corresponding to the <code>core.fnl</code>.</p><p>Let's require <code>user/core</code> module in <code>init.lua</code><pre><code class="lang-lua">-- init.lua
-- ...
-- append this to the bottom of init.lua file.
require&#40;'user.core'&#41;
</code></pre></p><p>Now, when you restart neovim, it greets you with:<blockquote><p> Hello, world! This is fennel config!  </p></blockquote>Hop on to the <a href='/neovim-clojure-pde-2'>next part</a>.</p>]]></content>
  </entry>
  <entry>
    <id>https://github.com/borkdude/quickblog/quickblog-setup.html</id>
    <link href="https://github.com/borkdude/quickblog/quickblog-setup.html"/>
    <title>Self hosting personal blog</title>
    <updated>2024-01-10T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<blockquote><p> This post is aimed at a fairly technical audience. It expects familiarity with <em>clojure</em>, <em>babashka</em>, <em>git</em>, <em>github</em>, <em>cloudflare</em> and <em>markdown</em> syntax. </p></blockquote><p><strong>Prerequisites</strong>: Install babashka and git</p><p>As mentioned in my previous <a href='/new-year-2024'>post</a>, I had a wish to start writing in public for the last couple of years. Mostly to frame my own thoughts in a structured manner and to keep a public record so others can relate or criticize.</p><p>Recently I found the amazing <a href='https://github.com/borkdude/quickblog'>quickblog</a>, that helped me get up and running with my first post within two hours.</p><p>Without wasting any more word, let's get started.</p><h2 id="outline">Outline</h2><p>In this post, we will</p><ul><li>Set up a blog repository using quickblog</li><li>Host it on github</li><li>Write a github action config to generate static files on push to <code>main</code> branch</li><li>Set up a CD pipeline on cloudflare to deploy the blog</li><li>Point a custom domain to the blog site</li></ul><h2 id="what&#95;is&#95;quickblog?">What is quickblog?</h2><p>Quickblog is a light-weight static blog engine for clojure and babashka. It allows you to write your posts in markdown, provides live reloading and generates SEO optimized static files.</p><h2 id="setting&#95;up&#95;quickblog">Setting up quickblog</h2><p>Create a directory that'll hold your blogging engine and blog posts. I will call it <code>blog</code>. Inside <code>blog</code> directory, create a <code>bb.edn</code> file. The structure looks like the following:</p><pre><code class="lang-bash">➜ tree blog 
blog
└── bb.edn

1 directory, 1 file
</code></pre><p>Let's add <em>quickblog</em> as a dependency in our <code>deps.edn</code>.</p><pre><code class="lang-clojure">;; deps.edn
{:deps {io.github.borkdude/quickblog {:git/tag &quot;v0.3.6&quot;
                                      :git/sha &quot;d00e14b1176416b7d7b88e6608b6975888208355&quot;}}}}
</code></pre><p>Replace the <code>:git/tag</code> and <code>:git/sha</code> with the latest ones in <a href='https://github.com/borkdude/quickblog'>quickblog repository</a>.</p><p>Now we will add a babashka task that hooks into <code>quickblog</code> api and exposes it as a command line tool.</p><pre><code class="lang-clojure">{:deps {...} ;; folded deps map

 :tasks
 {:requires &#40;&#91;quickblog.cli :as cli&#93;&#41;
  :init &#40;def opts {:blog-title &quot;&lt;Your Blog Title&gt;&quot;
                   :blog-description &quot;&lt;Short Blog Description&gt;&quot;
                   :about-link &quot;&lt;Your Website Link&gt;&quot;
                   :twitter-handle &quot;&lt;Twitter Handle&gt;&quot;}&#41;
  quickblog {:doc &quot;Start blogging quickly! Run `bb quickblog help` for details.&quot;
             :task &#40;cli/dispatch opts&#41;}}}
</code></pre><p>Replace all the options, with your own.</p><p>Execute <code>bb quickblog help</code>, it should print a detailed help text about using quickblog.</p><h2 id="first&#95;blog&#95;post">First blog post</h2><p>Now that we have quickblog setup, we will generate our first post. Quickblog command line has a <code>new</code> subcommand that allows us to do just that.</p><pre><code class="lang-bash">bb quickblog new --file my-first-blog-post.md --title &quot;My first blog post&quot;
</code></pre><p>The directory structure after the last command should look like the following<pre><code class="lang-bash">➜  blog tree .
.
├── bb.edn
└── posts
    └── my-first-blog-post.md

2 directories, 2 files
</code></pre></p><p>Run a live reloading server using <code>bb quickblog watch</code>, it spins up a http server on <code>localhost:1888</code> by default.</p><p>Write your blog post, initialize the <code>blog</code> directory as a git repository and commit your markdown changes. You would also want to add a <code>.gitignore</code> file that excludes some directories from committing like <code>.work</code>, <code>.lsp</code> and <code>.clj-kondo</code>. We will also exclude the <code>public</code> directory from the commit to keep the main branch clean.</p><h2 id="setting&#95;up&#95;github&#95;action">Setting up github action</h2><p>Via github action, we will</p><ul><li>Switch to <code>deploy</code> branch</li><li>Generate the static files</li><li>Commit and push them to the repository</li></ul><p>For this, we will need to allow github actions <code>write</code> permissions on the repository.</p><p>In your repository, go to the <strong>Settings > Actions > General</strong>. In the <code>Workflow permissions</code> select <strong>Read and write permissions</strong> for now. Later, you can spericy more granular permissions <a href='https://docs.github.com/actions/reference/authentication-in-a-workflow#modifying-the-permissions-for-the-github_token'>documented here</a>.</p><p>Create the github action config file: <code>.github/workflows/deploy.yml</code>.<pre><code class="lang-yml">on:
  push:
    branches:
      - main
env:
  TZ: Asia/Kolkata # Replace this with your time zone

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: git config
        run: |
          git config --global user.email &quot;&lt;Github Email&gt;&quot;
          git config --global user.name &quot;&lt;Github Username&gt;&quot;
      - name: Switch to deploy branch
        uses: actions/checkout@v4
        with:
          ref: deploy
          fetch-depth: 0
      - name: Merge main into deploy
        run: git merge origin/main
      - name: Setup babashka
        uses: just-sultanov/setup-babashka@v2
        with:
          version: 1.3.186 # Update this babashka version to latest
      - name: clean
        run: bb quickblog clean
      - name: build
        run: bb quickblog render
      - name: git
        run: |
          export CURRENT&#95;TIME=$&#40;date&#41;
          git add public
          git commit -m &quot;deploy @ ${CURRENT&#95;TIME}&quot;
          git push origin -u deploy
</code></pre></p><p>Replace the placeholders for github email and username, and other parameters. Apart from that the file should be self explanatory.</p><p>We will keep it in our local for now, and push it later to github.</p><h2 id="setting&#95;up&#95;cloudflare">Setting up cloudflare</h2><p>Create a new cloudflare account if you don't have it already. Log in to the account and go to <strong>Workers & Pages</strong> in the left panel.</p><ul><li>We will <em>create an application</em></li><li>Go to <strong>Pages</strong> tab.</li><li>And <strong>Connect to Git</strong></li></ul><p>A modal pops up, where you select your Github account and the repository to connect. You'll be redirected to github. Allow access to just the <code>blog</code> repository and continue.</p><p>Now we go to the next step, add the project name and set the <code>Production branch</code> to <strong>deploy</strong> and <code>Build output directory</code> to <strong>public</strong>. <em>Click and deploy</em>.</p><p>Cloudflare will consider changes on all the branches other than the <code>Production branch</code>, that we set in the previous step, as preview branches. This means that changes on these other branches will also trigger a build, consuming our precious build quota.</p><p>To disable the preview branches, we will go to the project we just setup <strong>-> Settings tab -> Build & deployment -> Configure preview deployments -> None -> Save</strong>. We are done with the setting up our cloudflare page. It should be visible at <code>&lt;project-identifier&gt;.pages.dev</code>.</p><blockquote><p> <strong>Note</strong>: Create the main and deploy branches and push them to our github repository. This will trigger github action build, after which cloudflare page build and you should see you first blog up on the url mentioned above. </p></blockquote><h2 id="setting&#95;up&#95;custom&#95;domain">Setting up custom domain</h2><p>Here I am assuming that you already have a domain bought and configured on cloudflare. If you don't, then there are a couple of platforms that I use to find and get best offer for domains:</p><ul><li><a href='https://www.namecheap.com/'>Namecheap</a></li><li><a href='https://www.godaddy.com/'>GoDaddy</a></li><li><a href='https://www.bigrock.in'>BigRock</a></li><li><a href='https://www.hostinger.in/domain-name-search'>Hostinger</a></li></ul><p>Once you get a domain, transfer it to cloudflare by following <a href='https://developers.cloudflare.com/registrar/get-started/transfer-domain-to-cloudflare/'>these steps</a>.</p><p>Setting up a custom domain for your page is simple. Open our page project, and go to <strong>Custom domains</strong> tab. Click on <strong>Set up a custom domain</strong> and follow the steps.</p><blockquote><p> <strong>PS</strong>: There are multiple ways to host your blog, and probably most of them would be simpler than this setup. It was my first time setting up my blog, and can certainly be improved. Will post an update when I migrate to a better pipeline. A similar post: <a href='https://www.eknert.com/blog/quickblog'>Quickblog by Anders Means Different</a> </p></blockquote><blockquote><p> If you find an error or have a question, open an <a href='https://github.com/Samy-33/blog/issues/new'>issue</a>. </p></blockquote>]]></content>
  </entry>
  <entry>
    <id>https://github.com/borkdude/quickblog/new-year-2024.html</id>
    <link href="https://github.com/borkdude/quickblog/new-year-2024.html"/>
    <title>New year 2024: Beginning</title>
    <updated>2024-01-01T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<p>Welcome to <strong>Abstract Symphony</strong>, fellow humans.</p><p>First off, a very happy new year. May you find love, happiness and joy this year.</p><h2 id="wedding">Wedding</h2><p>2023, for me, was life changing. In a good way. I got married to my beautiful wife, spent more time with family (thanks to my new job at <a href='https://oliv.ai'>Oliv</a>), read a lot of books and healed from wounds of the past. I can not be grateful enough for this phase of my life.</p><p><img src="./assets/wedding.jpeg" alt="Wedding" width="100%" /></p><p>The wedding brought my family closer. It was tiring to the core to the whole family, but enjoyable nonetheless. I usually stay away from the crowd and excess stimuli but bent these boundaries for the family, and it turned out fine. Like all the things in nature, the excitement and joy came back to the normal level. Now, there are photos and videos of these moments, that we will cherish for the next several years.</p><h2 id="new&#95;job&#95;at&#95;oliv">New Job at Oliv</h2><p>I have an itch of experimenting and reading about new things, specifically in the technology industry. For the last couple of years, I wanted to read and learn about <a href='https://clojure.org/'>clojure</a> - a dynamic, functional and lisp flavoured programming language. The idea of REPLs fascinated me from the very first moment I read about them. So, I went ahead and tried to build a project with it. I failed miserably, it was hard to alter my thinking to align with the principles that clojure stood for.</p><p>Later when <a href='https://www.linkedin.com/in/dvdreddy/'>dvd</a> contacted me about joining Oliv in the mid 2023, I couldn't say no. One of the reasons was <em>clojure</em> based tech stack at Oliv.</p><p>I hopped on the clojure journey and haven't looked back. I've come to love clojure due to following reasons:</p><ul><li>REPL driven development</li><li>Relatively small and growth driven community</li><li>Open source orientation</li><li>Down to earth moderators and builders</li></ul><p>There are probably more, that I don't recall right now. Join the Official <a href='https://join.slack.com/t/clojurians/shared_invite/zt-29tr0sxes-m26dRDptHFQSdVULhDALgQ'>Clojurians Slack</a> if you're interested.</p><h2 id="books">Books</h2><p>Reading, as a habit, began when I faced an identity crisis during my undergraduate studies. I was looking to improve myself, probably due to self-esteem issues. Since then, I have been continuously reading psychology, philosophy and self-help non-fiction. At a point of time, I was only reading the books and nothing else.</p><p>2023 was a moderate reading year. I bought 20+ books, finished 1 and still reading 4-5 books at a time. It's become a hobby more than anything else now. Reading is a stress releaver.</p><p>Following is a list of books I read in 2023.</p><ul><li><strong>Courage to be disliked</strong>: A paradigm shift is rare, and this book did it in a way that hit close to home. It has a dialog based writing style and I could see myself in one of the two main characters. A beginner friendly introduction to Adlerian Psychology, that promotes the idea that all the problems we face are interpersonal relationship problems. We can solve these problems by helping others and seeing them as our comrades.</li><li><strong>The beginning of infinity</strong>: In my teenage and college years, I was fascinated, as well as, troubled by the fundamental questions about universe, our existence and the lack of their answers. This book doesn't answer these questions but adds more perspectives for us to think about them. Loved every chapter of it.</li><li><strong>Goal: A Process of Ongoing Improvement</strong>: This book gives an idea about how to prioritise our work and what goals should we strive for in it.</li><li><strong>Designing data intensive application</strong>: Well, what can I say except "read this instead of taking those system design preparation courses". An enjoyable read about how large systems work and their most fundamental pieces.</li><li><strong>Atomic Habits</strong>: Science and psychology based habit building. James Clear, the author, makes it an enjoyable read by adding short stories about small algorithms we can implement to build good and destroy bad habits.</li></ul><p>It is not an exhaustive list of books I read in 2023. The above is the ones that I found most interesting and would recommend everyone to read.</p><h2 id="writing">Writing</h2><p>I came across a few people in last few years that have inspired me to write. Writing also helps me arrange and sharpen my thoughts. Having a brain that is susceptible to distraction poses a different set of problems to the quality of conversations and thoughts.</p><p>I wanted to start writing but before that I needed to build a blogging platform with a great design and implementation in <code>clojure</code>. Unfortunately, I always procrastinated as it was no simple task.</p><p>Late in 2023, I was going through the mighty <a href='https://github.com/borkdude'>borkdude's</a> socials and found the <a href='https://github.com/borkdude/quickblog'>quickblog</a>, a light-weight static blog engine for Clojure and babashka. I was writing my first blog within a minute. Super grateful for this product. Thanks Michiel.</p><h2 id="goals&#95;for&#95;2024">Goals for 2024</h2><p>I am not one of those people who choose new year resolutions at the beginning of the year and as it passes, forgets about them.</p><p>On a high level, I want to work on my family and help them reach a place where they could focus on what's important (Story about this sometime later). Focus on my marriage and get the fundamentals right in terms of our plans and family. I would also like to improve and be cosistent in writing on this blog and reading the books that I couldn't finish in 2023.</p><p>These are the most important ones and I look forward to and wish you all have, a great new year.</p><h2 id="quote&#95;for&#95;the&#95;post">Quote for the post</h2><blockquote><p> God give me serenity to accepts things I can not change, the courage to change that I can and wisdom to know the difference. </p></blockquote>]]></content>
  </entry>
</feed>
