<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ishan</title>
    <description>The latest articles on Forem by Ishan (@sadpixel).</description>
    <link>https://forem.com/sadpixel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F93037%2F7e42be51-0196-40a3-8a7c-adb77931eacc.png</url>
      <title>Forem: Ishan</title>
      <link>https://forem.com/sadpixel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sadpixel"/>
    <language>en</language>
    <item>
      <title>The Cult of Not Done Manifesto</title>
      <dc:creator>Ishan</dc:creator>
      <pubDate>Tue, 31 Oct 2023 00:00:00 +0000</pubDate>
      <link>https://forem.com/sadpixel/the-cult-of-not-done-manifesto-12b9</link>
      <guid>https://forem.com/sadpixel/the-cult-of-not-done-manifesto-12b9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;To all those who ask why I haven’t been putting more posts out, this is for you&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dear Members of the Cult of Not Done,&lt;/p&gt;

&lt;p&gt;I present to you a manifesto of not done. This was inspired by &lt;a href="https://medium.com/@bre/the-cult-of-done-manifesto-724ca1c2ff13"&gt;Kio Stark and Bre Pettis&lt;/a&gt;, and written over five days because it was, obviously, not done.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Cult of Not Done Manifesto
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;There are four states of being. Not knowing, action, refinement, and completion.&lt;/li&gt;
&lt;li&gt;Accept that everything is a draft. We have to make it better before we can be done.&lt;/li&gt;
&lt;li&gt;We live in the editing stage. Nothing is ever done because it’s always being improved.&lt;/li&gt;
&lt;li&gt;Pretending you know what you’re doing is almost the same as knowing what you are doing, but not good enough. So spend all your time making things perfect.&lt;/li&gt;
&lt;li&gt;Embrace procrastination. If you wait more than a week to get an idea done, give it more time. Your subconscious needs to think more, and it will give you the answer soon.&lt;/li&gt;
&lt;li&gt;The point of being done is to finish, and until we don’t, we can’t get other things done.&lt;/li&gt;
&lt;li&gt;If you’re never done, you can throw it away.&lt;/li&gt;
&lt;li&gt;Laugh at those who settle. It’s boring and keeps you from being perfection.&lt;/li&gt;
&lt;li&gt;People without dirty hands are wrong. Doing something makes you right. The more time you spend trying different things and not being done, the more authority you have on it.&lt;/li&gt;
&lt;li&gt;Failure and mistakes are natural and to be expected since you’re not done yet.&lt;/li&gt;
&lt;li&gt;Destruction is a force of progress. Start over often with increased scope to make your creation perfect.&lt;/li&gt;
&lt;li&gt;If you have an idea, never publish it on the internet, then it will be stolen from you.&lt;/li&gt;
&lt;li&gt;In Progress is the engine of more.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>humor</category>
    </item>
    <item>
      <title>Dear past me, use the flags</title>
      <dc:creator>Ishan</dc:creator>
      <pubDate>Wed, 26 Jul 2023 00:00:00 +0000</pubDate>
      <link>https://forem.com/sadpixel/dear-past-me-use-the-flags-oci</link>
      <guid>https://forem.com/sadpixel/dear-past-me-use-the-flags-oci</guid>
      <description>&lt;p&gt;Dear past me,&lt;/p&gt;

&lt;p&gt;I know you’ve just launched your first desktop application. It’s a nightmare isn’t it? Well you’re in for a bigger one.&lt;/p&gt;

&lt;p&gt;Your boss calls you, and they need a new feature immediately. You bang it out and send out the update.&lt;/p&gt;

&lt;p&gt;Oh yeah, that’s right. You didn’t include an update mechanism. Please use one, you’ll save yourself a lot of pain.&lt;/p&gt;

&lt;p&gt;You send out the update to users, but soon you find out everyone hasn’t updated yet. The boss &lt;strong&gt;insists&lt;/strong&gt; that people on an older app version shouldn’t be able to use it.&lt;/p&gt;

&lt;p&gt;But you didn’t include a forced version based lockout mechanism either. Well, you’ll remember that for next time!&lt;/p&gt;

&lt;p&gt;Desperate times call for desperate measures, and you decide to break the server so it returns bad data to old app versions. Never mind that this permanently corrupts the local database and will require future tech support to help them clear their cache. But that’s future you’s problem.&lt;/p&gt;

&lt;p&gt;The boss is happy, the app is chugging along. This cycle repeats a couple more times in various ways.&lt;/p&gt;

&lt;p&gt;Save yourself the pain next time. Implement a feature flag / version system. Later, it’ll even help you roll out features to users incrementally.&lt;/p&gt;

&lt;p&gt;Use the flags, Luke.&lt;/p&gt;

&lt;p&gt;Sincerely, Present me&lt;/p&gt;

</description>
      <category>story</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>Programming with the grain</title>
      <dc:creator>Ishan</dc:creator>
      <pubDate>Sun, 09 Jul 2023 00:00:00 +0000</pubDate>
      <link>https://forem.com/sadpixel/programming-with-the-grain-22ph</link>
      <guid>https://forem.com/sadpixel/programming-with-the-grain-22ph</guid>
      <description>&lt;p&gt;When I was a kid, I observed that most sheets of paper have a sort of "direction" inside them. It's not possible to tell by looking, but there's a simple experiment to show:&lt;/p&gt;

&lt;p&gt;Rip the same sheet of paper left and right, then up and down. One of the two will have a smooth, straight, nice-looking rip, while the other will have a rough, uneven edge.&lt;/p&gt;

&lt;p&gt;Later, as a teenager, I learned that this is called the "grain" of the paper, and that this also applies to &lt;a href="https://gillette.com/en-us/shaving-tips/how-to-shave/against-the-grain"&gt;shaving&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Wood_grain"&gt;woodworking&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have observed that computers and programs exhibit similiar characteristics which aren't obvious at first. Any given computer, platform, problem and program has a "grain" too. A hidden, but present underlying "optimal" (may I even call it "natural") structure of how data flows and the factorization of the various components that would constitute it. &lt;a href="https://wiki.c2.com/?WithTheGrain"&gt;Others&lt;/a&gt; seem to have observed it too.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tryst with locality
&lt;/h2&gt;

&lt;p&gt;Memory can be a funny thing, both when it comes to the self, and to computers. Take the following two pieces of C++ code for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// First Example&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="c1"&gt;// Do something with the thing item&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Second Example&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="c1"&gt;// Do something with the thing item&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running 100 iterations on a 10x10 array filled with 0s, the first example takes 221ns and the second takes 223ns. That's not even 1%, and maybe it can be chalked up to statistial error. But what happens when we go bigger?&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Array Size&lt;/th&gt;
&lt;th&gt;Average 1&lt;/th&gt;
&lt;th&gt;Average 2&lt;/th&gt;
&lt;th&gt;Best 1&lt;/th&gt;
&lt;th&gt;Best 2&lt;/th&gt;
&lt;th&gt;Worst 1&lt;/th&gt;
&lt;th&gt;Worst 2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10x10&lt;/td&gt;
&lt;td&gt;221.52ns&lt;/td&gt;
&lt;td&gt;223.42ns&lt;/td&gt;
&lt;td&gt;205ns&lt;/td&gt;
&lt;td&gt;211ns&lt;/td&gt;
&lt;td&gt;480ns&lt;/td&gt;
&lt;td&gt;402ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100x100&lt;/td&gt;
&lt;td&gt;24646ns&lt;/td&gt;
&lt;td&gt;13296ns&lt;/td&gt;
&lt;td&gt;8163ns&lt;/td&gt;
&lt;td&gt;8266ns&lt;/td&gt;
&lt;td&gt;143007ns&lt;/td&gt;
&lt;td&gt;32424ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1000x1000&lt;/td&gt;
&lt;td&gt;806953ns&lt;/td&gt;
&lt;td&gt;1.33ms&lt;/td&gt;
&lt;td&gt;645242ns&lt;/td&gt;
&lt;td&gt;951179ns&lt;/td&gt;
&lt;td&gt;1.5ms&lt;/td&gt;
&lt;td&gt;3.44ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10000x10000&lt;/td&gt;
&lt;td&gt;81.3ms&lt;/td&gt;
&lt;td&gt;675.34ms&lt;/td&gt;
&lt;td&gt;70.85ms&lt;/td&gt;
&lt;td&gt;632.21ms&lt;/td&gt;
&lt;td&gt;101.65ms&lt;/td&gt;
&lt;td&gt;776.79ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nYnbkFo0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ishan.page/blog/2023-07-09-programming-with-the-grain/locality-graph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nYnbkFo0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ishan.page/blog/2023-07-09-programming-with-the-grain/locality-graph.png" alt="Graph" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first example performs dramatically better! But why? What is it doing different? Let's take a closer look:&lt;/p&gt;

&lt;p&gt;Imagine a 3 x 3 array. It might look 2D to us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,2,3
4,5,6
7,8,9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, 2D arrays are an abstraction. Memory in computers is single dimensional, so the computer stores this as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,2,3,4,5,6,7,8,9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This drastic performance difference is because of the concept of &lt;a href="https://eng.libretexts.org/Courses/Delta_College/Operating_System%3A_The_Basics/01%3A_The_Basics_-_An_Overview/1.7_Cache_Memory/1.7.2_Cache_Memory_-_Locality_of_reference"&gt;Locality of Reference&lt;/a&gt; and how C++ stores your data in memory. Since C++ uses &lt;a href="https://en.wikipedia.org/wiki/Row-_and_column-major_order"&gt;row-major&lt;/a&gt; ordering, accessing elements in order of rows (as in the first example) goes &lt;em&gt;with the grain&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Since the elements are stored are more likely to be located nearby in memory, the processor can also "cheat" and load a contiguous chunk of memory into the cache, which allows for much faster access. However, when iterating by column (or &lt;em&gt;against the grain&lt;/em&gt;), the processor can't cache the columns from memory, because &lt;em&gt;the columns don't exist next to each other&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;There's more to the story than performance though.&lt;/p&gt;

&lt;h2&gt;
  
  
  The terrible tale of pthreads
&lt;/h2&gt;

&lt;p&gt;When someone says "multithreading", they almost always mean threading with reference to the POSIX threads model. People &lt;a href="https://softwareengineering.stackexchange.com/questions/81003/how-to-explain-why-multi-threading-is-difficult"&gt;often complain&lt;/a&gt; about how difficult and error prone the usage of threads are.&lt;/p&gt;

&lt;p&gt;However, what most people won't tell you, is that &lt;strong&gt;POSIX threads are against the grain of computers&lt;/strong&gt;. While threads can run concurrently, the underlying architecture does not directly resemble the interrupt-driven behavior of CPUs. Thread scheduling and synchronization are typically handled by the operating system, abstracting away the low-level hardware details. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://swtch.com/~rsc/thread/"&gt;Communicating Sequential Processes&lt;/a&gt; (CSP) concurrency model, pioneered by Tony Hoare, focuses on communication and synchronization between concurrent processes rather than explicit thread management. In CSP, processes communicate by sending and receiving messages through channels, and synchronization is achieved through the coordination of message passing. This model aligns more closely with the interrupt-based architecture of CPUs. Go is based on this model.&lt;/p&gt;

&lt;h2&gt;
  
  
  When in Rome
&lt;/h2&gt;

&lt;p&gt;When in Rome, one should always do as Romans do. When programming, this often extends to following the idioms of the platform and language you are working on, as well as the culture and coding guidelines of the team. That's why, when in Python land, you follow the Zen of Python, and in Go land, you don't use a full stack framework. This is because going against the grain of the platform makes it more difficult, and you'll definitely know when you're doing it.&lt;/p&gt;

&lt;p&gt;As &lt;a href="http://web.archive.org/web/20201025220805/https://sites.google.com/site/yacoset/Home/signs-that-you-re-a-good-programmer"&gt;yacoset&lt;/a&gt; puts it so elegantly,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You cannot think "Fire rearward missile" and then translate it to Russian, you &lt;strong&gt;must think in Russian&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Consider the following program in Python that someone from a C background might write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nb"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sum: %d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This goes against the grain, and is non idiomatic Python.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nb"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sum: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idiomatic Python version utilizes the built-in sum() function with a range to directly calculate the sum of numbers from 1 to 10. It then uses an f-string to format the output and achieves the same functionality in a more concise and Pythonic way.&lt;/p&gt;

&lt;p&gt;How about this code in Go?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"io"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"sync"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"https://example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"https://google.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"https://github.com"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mu&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error fetching %s: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error reading response body from %s: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"URL: %s, Length: %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we manually manage the "threads" using the &lt;code&gt;sync.WaitGroup&lt;/code&gt;. Each URL is processed by a separate thread, and synchronization is achieved using the &lt;code&gt;sync.Mutex&lt;/code&gt; to protect shared access to the console output.&lt;/p&gt;

&lt;p&gt;While this approach still accomplishes concurrent processing, it deviates from the idiomatic use of goroutines and channels in Go and involves lower-level thread management.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    urls := []string{"https://example.com", "https://google.com", "https://github.com"}

    // Create a channel to receive the results
    results := make(chan string)

    // Launch a goroutine for each URL
    for _, url := range urls {
        go fetchURL(url, results)
    }

    // Collect the results
    for i := 0; i &amp;lt; len(urls); i++ {
        result := &amp;lt;-results
        fmt.Println("Fetched:", result)
    }
}

func fetchURL(url string, results chan&amp;lt;- string) {
    resp, err := http.Get(url)
    if err != nil {
        results &amp;lt;- fmt.Sprintf("Error fetching %s: %s", url, err.Error())
        return
    }

    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        results &amp;lt;- fmt.Sprintf("Error reading response body from %s: %s", url, err.Error())
        return
    }

    results &amp;lt;- fmt.Sprintf("URL: %s, Length: %d", url, len(body))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we launch a goroutine for each URL, and each goroutine fetches the content of the URL. The results are then sent back through the results channel. Finally, we collect the results and print them.&lt;/p&gt;

&lt;p&gt;This approach, (while still not optimal) aligns with the idiomatic way of utilizing goroutines and channels for concurrent tasks in Go and is much more readable and thus maintainable in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;Further quoting yacoset,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There's a thousand computer languages because there's a thousand classes of problems we can solve with software. In the 1980s, after the Macintosh debut, a hundred DOS products were ported to the new mouse-driven platform by clubbing the Alto-inspired UI over the head and brute-forcing the keyboard-driven paradigms of PCs into the Mac's visual atmosphere. Most of these were rejected by Apple or the market, and if they came back for a second try they came back because somebody flipped open the spiral-bound HIG and read it sincerely. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As programmers, I think we should take pride in our craft, and always try to program with the grain of the platform we are on. Many benefits await!&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>python</category>
      <category>go</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>The secret life of .well-known</title>
      <dc:creator>Ishan</dc:creator>
      <pubDate>Sun, 02 Jul 2023 00:00:00 +0000</pubDate>
      <link>https://forem.com/sadpixel/the-secret-life-of-well-known-1h5i</link>
      <guid>https://forem.com/sadpixel/the-secret-life-of-well-known-1h5i</guid>
      <description>&lt;p&gt;My first encounter with the &lt;code&gt;.well-known&lt;/code&gt; directory was when Let’s Encrypt first hit the scene and I was learning about VPS-es and how Linux on the server worked.&lt;/p&gt;

&lt;p&gt;I noticed that it used certain URLs like &lt;code&gt;.well-known/acme-challenge/3d2f4b8c9d0a4f8b9e6f7d8c9e0a4f8b&lt;/code&gt;. This led me down a nice rabbit hole about the ACME protocol, but that’s a story for another time.&lt;/p&gt;

&lt;p&gt;Years later, when configuring servers to use paid HTTPs certificates, I noticed the use of the &lt;code&gt;.well-known/pki-validation/&lt;/code&gt; directory for verifying certificates. I filed this knowledge away in my brain as “the &lt;code&gt;.well-known&lt;/code&gt; directory is for SSL related stuff” and continued on with my life.&lt;/p&gt;

&lt;p&gt;Somewhere in the middle, I noticed that a lot of brute force attacks on servers I monitored would be directed to &lt;code&gt;.well-known&lt;/code&gt; URLs. At that point, I had learned about DNS based verification, and quickly moved on from HTTP challenges, and blocked access to the directory in my web server configs. I made this a part of my base setup on every server, and didn’t really think too much about the directory any more.&lt;/p&gt;

&lt;p&gt;That was, until &lt;em&gt;very&lt;/em&gt; recently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fiasco with NodeInfo
&lt;/h2&gt;

&lt;p&gt;As a recent migrant to the Fediverse after the Reddit API changes shut down my 3rd party app of choice, I joined a fairly interesting Lemmy instance, and I quickly found the distributed nature fascinating. All I could think about was: “I want to see a giant map of all the instances and who is connected to who”, and immediately set out to create a web scraper that could gather this information for me.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don’t worry, I made sure to respect &lt;code&gt;robots.txt&lt;/code&gt; for every site the scraper visited.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lemmy’s API documentation is fairly poor, and I flailed for quite a while before I discovered the endpoint (&lt;code&gt;/api/v3/site&lt;/code&gt;) that would give me the list of federated servers.&lt;/p&gt;

&lt;p&gt;That’s when I realized, that Lemmy could &lt;em&gt;and did&lt;/em&gt; federate with other Fediverse software like Mastodon, Calckey, Pleroma, WriteFreely, PixelFed and more. And each one of these have different APIs and different endpoints that would give me the data I need.&lt;/p&gt;

&lt;p&gt;I needed a way to detect which software each server was running. My first instinct was to use some sort of heuristic, but that’s unreliable, so I continued researching and discovered the &lt;a href="https://github.com/jhass/nodeinfo/blob/main/PROTOCOL.md"&gt;NodeInfo&lt;/a&gt; protocol.&lt;/p&gt;

&lt;p&gt;And wouldn’t you have it, the resource I need to request &lt;em&gt;just happens to be&lt;/em&gt;_&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/.well-known/nodeinfo

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, my curiousity was piqued. There was more to this &lt;code&gt;.well-known&lt;/code&gt; directory than I had initially thought!&lt;/p&gt;

&lt;h2&gt;
  
  
  The RFC enters the fray
&lt;/h2&gt;

&lt;p&gt;It &lt;a href="https://serverfault.com/questions/795467/what-is-the-purpose-of-the-well-known-folder"&gt;turns out&lt;/a&gt;, that the .well-known directory is defined in &lt;a href="https://www.rfc-editor.org/rfc/rfc8615"&gt;RFC 8615&lt;/a&gt;. The intention seems to be to make it like a generic and extendable version of &lt;code&gt;robots.txt&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is increasingly common for Web-based protocols to require the discovery of policy or other information about a host (“site-wide metadata”) before making a request. For example, the Robots Exclusion Protocol &lt;a href="http://www.robotstxt.org/"&gt;http://www.robotstxt.org/&lt;/a&gt; specifies a way for automated processes to obtain permission to access resources; likewise, the Platform for Privacy Preferences [W3C.REC-P3P-20020416] tells user-agents how to discover privacy policy beforehand.&lt;/p&gt;

&lt;p&gt;While there are several ways to access per-resource metadata (e.g., HTTP headers, WebDAV’s PROPFIND [RFC4918]), the perceived overhead (either in terms of client-perceived latency and/or deployment difficulties) associated with them often precludes their use in these scenarios.&lt;/p&gt;

&lt;p&gt;When this happens, it is common to designate a “well-known location” for such data, so that it can be easily located. However, this approach has the drawback of risking collisions, both with other such designated “well-known locations” and with pre-existing resources.&lt;/p&gt;

&lt;p&gt;To address this, this memo defines a path prefix in HTTP(S) URIs for these “well-known locations”, /.well-known/. Future specifications that need to define a resource for such site-wide metadata can register their use to avoid collisions and minimise impingement upon sites' URI space.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There’s a whole &lt;a href="https://en.wikipedia.org/w/index.php?title=Well-known_URI#List_of_well-known_URIs"&gt;&lt;strong&gt;bunch&lt;/strong&gt; of stuff&lt;/a&gt; that you can do with stuff in the &lt;code&gt;.well-known&lt;/code&gt; folder, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.hardill.me.uk/wordpress/2021/01/24/email-autoconfiguration/"&gt;Automate email client configuration for self hosted email&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://web.dev/change-password-url/"&gt;Let password managers automatically know which URL to visit to change a password&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://platform.openai.com/docs/plugins/getting-started"&gt;Let ChatGPT discover your plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spec.matrix.org/latest/client-server-api/#well-known-uri"&gt;Discover Matrix server details&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and of course, let’s not forget about &lt;a href="https://webfinger.net/"&gt;Webfinger&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“A webfinger? Is that what I use to poke someone on Facebook?” - Anonymous&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;UPDATE 2023-07-04&lt;/strong&gt; : I have published a follow-up - &lt;a href="https://ishan.page/blog/2023-07-04-addendum-webfinger"&gt;Addendum: Webfinger&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE 2023-07-07&lt;/strong&gt; : This article was featured in &lt;a href="https://tldr.tech/tech/2023-07-07"&gt;tldr.tech&lt;/a&gt;, and as of time of writing I have had over 6500 hits on this post. I am overwhelmed and moved by this, and by all the positive comments I have received. I never imagined that my little blog would ever be read by so many people.&lt;/p&gt;

</description>
      <category>webdev</category>
    </item>
    <item>
      <title>Using different Github accounts with different private keys on Linux</title>
      <dc:creator>Ishan</dc:creator>
      <pubDate>Wed, 21 Jun 2023 00:00:00 +0000</pubDate>
      <link>https://forem.com/sadpixel/using-different-github-accounts-with-different-private-keys-on-linux-5ao</link>
      <guid>https://forem.com/sadpixel/using-different-github-accounts-with-different-private-keys-on-linux-5ao</guid>
      <description>&lt;p&gt;Create a file &lt;code&gt;~/.ssh/config&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you have 2 keys, for example: &lt;code&gt;id_rsa&lt;/code&gt; for your personal, and &lt;code&gt;id_work&lt;/code&gt; for your work, set the config as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host github-work
    HostName github.com
    IdentityFile ~/.ssh/id_work
    IdentitiesOnly yes

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when cloning or adding remote, change the &lt;code&gt;github.com&lt;/code&gt; in the clone url is changed to &lt;code&gt;github-work&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone git@github-work:username/whatever.git

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>linux</category>
      <category>tips</category>
    </item>
    <item>
      <title>Collapsible Comments on Dev.to</title>
      <dc:creator>Ishan</dc:creator>
      <pubDate>Sun, 26 Aug 2018 04:37:57 +0000</pubDate>
      <link>https://forem.com/sadpixel/collapsible-comments-on-devto-5785</link>
      <guid>https://forem.com/sadpixel/collapsible-comments-on-devto-5785</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Note from the Future&lt;br&gt;
It's 2021, and dev.to has this feature now!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hey everyone! First time posting here!&lt;/p&gt;

&lt;p&gt;I love dev.to, but as an avid redditor, the lack of collapsible comments always kind of bothered me-- I mean, it's a feature that should totally exist. There's even a &lt;a href="https://github.com/thepracticaldev/dev.to/issues/173"&gt;GitHub Issue&lt;/a&gt; for it.&lt;/p&gt;

&lt;p&gt;So, I decided to finish a side-project for once. The result, a chrome extension that adds this very feature to dev.to. &lt;/p&gt;

&lt;p&gt;Installation is a bit of a challenge as I'm pretty new to this making extensions thing, hopefully someone will be able to help me out with that.&lt;/p&gt;

&lt;p&gt;Repo link: &lt;a href="https://github.com/sad-pixel/dev-to-comment-collapser" rel="noopener noreferrer"&gt;https://github.com/sad-pixel/dev-to-comment-collapser&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation Instructions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Download the zip file or clone the repo&lt;/li&gt;
&lt;li&gt;Go to &lt;code&gt;chrome://extensions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enable the developer mode toggle&lt;/li&gt;
&lt;li&gt;Click "Load Unpacked"&lt;/li&gt;
&lt;li&gt;Browse to the folder where the source files are located&lt;/li&gt;
&lt;li&gt;Done&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In case the buttons don't show up, refreshing the page should fix the issue. This is caused due to the way dev.to loads new pages.&lt;/p&gt;

&lt;p&gt;Hopefully this will be of some help to someone!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
