Jogendra Personal website about me and technical/aviation stuffs I find useful. https://jogendra.dev/ Tue, 05 Nov 2024 12:50:05 +0000 Tue, 05 Nov 2024 12:50:05 +0000 Jekyll v3.10.0 Writing maintainable Go code <p>Writing maintainable code is essential. Clarity, readability, and simplicity are all aspects of maintainability. It should make the process easy for someone to join your project or maintain it after someone leaves. Maintainability is measured by how effortless it is to introduce changes and the amount of risk associated with those changes. To write Go effectively, it is crucial to understand its properties and idioms and apply the established conventions related to naming, program construction, formatting, etc.</p> <blockquote> <p><strong>Note</strong>: This article was originally published on <a href="https://deepsource.io/learn/software-engineering-guide/writing-maintainable-go-code/">Deepsource blogs</a>.</p> </blockquote> <p>Here are some good practices that will help write maintainable Go code.</p> <h3 id="keep-main-small">Keep <code class="language-plaintext highlighter-rouge">main</code> small</h3> <p>“<a href="https://go.dev/tour/basics/1">Tour of Go</a>” reads:</p> <blockquote> <p>Every Go program is made up of packages. Programs start running in package <code class="language-plaintext highlighter-rouge">main</code>.</p> </blockquote> <p><strong>main</strong> package is unique as neither the exported names are exported, nor does the compiler treat it like a regular package; instead, it compiles it to an executable program. Inside <strong>main</strong> package, <strong>main</strong> function (<code class="language-plaintext highlighter-rouge">main.main</code>) is present, which is the entry point to a Go program. Expectation from the <code class="language-plaintext highlighter-rouge">main</code> package and the <code class="language-plaintext highlighter-rouge">main</code> function is that they do as little as possible.</p> <p><strong>main.main</strong> acts as a singleton and gets only called once. It is also hard to write tests for the code inside <code class="language-plaintext highlighter-rouge">main.main</code>, thus, it is highly recommended to drive the program with <em>main.main</em> but not write the business logic inside <code class="language-plaintext highlighter-rouge">main</code> package. Segregating driver and business logic in separate packages improves the program’s structure and maintainability.</p> <h3 id="use-meaningful-names">Use meaningful names</h3> <p>Naming in Go gives major emphasis to — consistent, short and accurate names as they tend to improve the readability of the code.</p> <p>Russ Cox’s <a href="https://research.swtch.com/names">philosophy</a> on naming:</p> <blockquote> <p>A name’s length should not exceed its information content. For a local variable, the name <code class="language-plaintext highlighter-rouge">i</code> conveys as much information as <code class="language-plaintext highlighter-rouge">index</code> or <code class="language-plaintext highlighter-rouge">idx</code> and is quicker to read. Similarly, <code class="language-plaintext highlighter-rouge">i</code> and <code class="language-plaintext highlighter-rouge">j</code> are a better pair of names for index variables than <code class="language-plaintext highlighter-rouge">i1</code> and <code class="language-plaintext highlighter-rouge">i2</code> (or, worse, <code class="language-plaintext highlighter-rouge">index1</code> and <code class="language-plaintext highlighter-rouge">index2</code>), because they are easier to tell apart when skimming the program. Global names must convey relatively more information because they appear in a larger variety of contexts. Even so, a short, precise name can say more than a long-winded one: compare <code class="language-plaintext highlighter-rouge">acquire</code> and <code class="language-plaintext highlighter-rouge">take_ownership</code>. Make every name <a href="http://www.bartleby.com/141/strunk5.html#13">tell</a>.</p> </blockquote> <p>There’s a high chance that years of programming experience and naming philosophies of Ken Thompson, Rob Pike, Robert Griesemer, Russ Cox, Ian Lance Taylor, etc. might have inspired the naming conventions in Go. <a href="https://talks.golang.org/2014/names.slide">Here’s</a> a slide by Andrew Gerrand which talks about naming in Go in more depth.</p> <h3 id="code-grouping">Code grouping</h3> <p>Within a function (or method), some statements could be correlated. Therefore, keeping those statements within separate code blocks is recommended, separated by a newline. Grouping makes program construction better and improves readability by segregating the related parts.</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// Creating new HTTP request with request body</span> <span class="n">req</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">NewRequest</span><span class="p">(</span><span class="s">"POST"</span><span class="p">,</span> <span class="s">"https://api.example.com/endpoint"</span><span class="p">,</span> <span class="n">body</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="c">// handle err</span> <span class="p">}</span> <span class="c">// Setting headers to HTTP request</span> <span class="n">req</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span> <span class="s">"application/json"</span><span class="p">)</span> <span class="n">req</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"Authorization"</span><span class="p">,</span> <span class="s">"Bearer b7d03a6947b217efb6f3ec3bd3504582"</span><span class="p">)</span> <span class="c">// Executing the request</span> <span class="n">resp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">DefaultClient</span><span class="o">.</span><span class="n">Do</span><span class="p">(</span><span class="n">req</span><span class="p">)</span> <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span> <span class="c">// handle err</span> <span class="p">}</span> <span class="k">defer</span> <span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span> </code></pre></div></div> <h3 id="write-meaningful-comments">Write meaningful comments</h3> <p>Comments are an excellent way to understand existing code, and it answers what it does and why some piece of code exists and why it was written like this. It is good to write comments while writing the code, but it is more important to update the comments whenever you update the code. Code updates may change the objective of a particular code, so it is crucial to update comments too; otherwise, it will create confusion rather than helping later. It is better not to write comments than comments that contradict the code. You can write block comments or inline comments in Go, whatever suits your code better.</p> <p>Commenting your code correctly in Go will also allow you to use the <em>godoc</em> tool. <a href="https://blog.golang.org/godoc">godoc</a> will extract comments from your code and generate documentation for your Go program. Comments in Go have several good features, like <code class="language-plaintext highlighter-rouge">+build</code>, <code class="language-plaintext highlighter-rouge">go:generate</code>, <code class="language-plaintext highlighter-rouge">cgo</code>, to name a few. Refer to <a href="https://blog.jbowen.dev/2019/09/the-magic-of-go-comments/">The Magic of Go Comments</a> to understand in detail.</p> <p>Knowing what not to write in comments is as important as knowing what to write. It would be best to avoid over-commenting the code and leave it to other programmers to understand Go. You should avoid obvious comments, and if the code is readable enough, it doesn’t need comments. For example:</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// Get country code from customer address.</span> <span class="n">countryCode</span> <span class="o">:=</span> <span class="n">getCountryCode</span><span class="p">(</span><span class="n">address</span><span class="p">)</span> <span class="p">{</span> <span class="c">// If country code is "IN", assign "India" as</span> <span class="c">// country.</span> <span class="k">if</span> <span class="n">countryCode</span> <span class="o">==</span> <span class="s">"IN"</span> <span class="p">{</span> <span class="n">country</span> <span class="o">=</span> <span class="s">"India"</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Comments written here are very obvious respective to code and don’t add any value.</p> <h3 id="dont-repeat">Don’t repeat</h3> <p>DRY (Don’t Repeat Yourself) is a principle of software development aimed at reducing duplication of software patterns, replacing it with abstractions to avoid redundancy.</p> <p>So, what’s the problem with code repetition? Not much until there’s a change. Imagine repeating the same code in 10 places and then changing at all 10 places whenever something small changes. It’s easier to maintain code if it only exists in one place, ensuring consistency. If the code is duplicated, there’s a good chance that you’ll forget to update one of the copies, meaning the bugs you fix in one copy will still be there in the other copy.</p> <p>If you have to write the same code again, move it into the shared package where most helper functions lie. With the removal of duplicate code, you will have less code that will be clearer and easier to maintain.</p> <p>The absence of generics (up to Go 1.17) might make Go code look repetitive in some places. But with generics being officially supported from Go 1.18 would make writing generic code a lot simpler, less redundant, and much more maintainable.</p> <p>But, sometimes repeating code is simpler and easier to understand than trying to force an abstraction to avoid redundancy. So it is more important to know where to apply DRY and where to not because code readability and ease of understanding trumps other concerns.</p> <h3 id="linting-and-style-guidelines">Linting and style guidelines</h3> <p>Adhering to a coding standard makes the codebase consistent and easy to review and maintain. It makes the coding style consistent. Often, the style guides are opinionated, and the best way to make people adhere to the same guidelines is to create a standard style guideline for the Go code. Having a style guide is not essential, but it is more critical to make your team use it religiously. There are many open-source linting tools and style guidelines out there, and you can take the base from there and modify it to make it works for you.</p> <p>Go has a code formatting standard generally used and accepted in the community, although it does not require special rules. Go provides the <code class="language-plaintext highlighter-rouge">gofmt</code> tool to encourage and safeguard that the Go code is formatted with established conventions.</p> <p>Many editors that support integration with <code class="language-plaintext highlighter-rouge">gofmt</code> format the file upon saving the file. Alternatively, <a href="https://github.com/mvdan/gofumpt">gofumpt</a>, a stricter version of <code class="language-plaintext highlighter-rouge">gofmt</code> can be used as well. The tool is a modified fork of <code class="language-plaintext highlighter-rouge">gofmt</code>, which can be used as a drop-in replacement. Also, these tools do support custom source transformations and the addition of custom rules.</p> <p>If you want to follow Go’s community-style guidelines, you can use <a href="https://github.com/golang/lint">golint</a>. The tool gives helpful hints on code style and can also help review the Go’s recognized conventions. This will dramatically help onboard each new developer added to the project.</p> <h3 id="avoid-deep-nesting">Avoid deep nesting</h3> <p>Excessive nesting bothers everyone. Deep nesting makes it hard to follow logic. If you’re doing code reviews or revisiting your old code, oversized functions (or methods) with lots of nesting become a mess of logical workflows. Also, nested code is hard to read; the lesser the nesting, the lesser is the cognitive load on the reader.</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">condition1</span> <span class="p">{</span> <span class="k">if</span> <span class="n">condition2</span> <span class="p">{</span> <span class="k">if</span> <span class="n">condition3</span> <span class="p">{</span> <span class="c">// ...</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c">// ...</span> <span class="p">}</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="k">if</span> <span class="n">condition5</span> <span class="p">{</span> <span class="c">// ...</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>There are several ways developers can avoid it. <a href="https://en.wikibooks.org/wiki/Computer_Programming/Coding_Style/Minimize_nesting">Here</a> is a good read about it.</p> <h3 id="write-better-functions">Write better functions</h3> <p>Avoid writing longer functions; smaller functions are better. Longer functions can be hard to read, test, and maintain. Longer functions often have bigger responsibilities, so it is recommended to break them into smaller functions. More callers can use shorter functions created from breaking down the longer functions as they now serve more managed, independent tasks.</p> <p>Doug McIlroy, the inventor of Unix pipes and one of the founders of the Unix tradition, said (Unix Philosophy):</p> <blockquote> <p>Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new features.</p> </blockquote> <p>So breaking down function to do one thing well does resonate well with the Unix Philosophy.</p> <p>As discussed earlier, naming is crucial to readability. Good function names are better than comments, and they can be just as helpful in aiding the understanding of code as a well-written comment or API documentation. Try to keep fewer function parameters; they are always better.</p> <h3 id="avoid-package-level-state">Avoid package level state</h3> <p>In Go, only one instance of a package exists for any given import path. This means that there is only one instance of any variable at the package level. Package level variables are shared across the global level, which means that all accessors will share the same instance. Function X can modify the variable, and function Y can read the modified value.</p> <p>The use of package-level variables can have many implications:</p> <ul> <li>It’s hard to trace where the variable got modified and where it got accessed to make any decision.</li> <li>Package-level variables cause tight coupling; change in one corner of code may require a modification in another corner of code, making it tougher to read, modify, and unit test the code.</li> <li>It may cause problems such as race conditions.</li> </ul> <p>However, the use of package-level constants is excellent. So it is always recommended to avoid using the package-level states as much as possible. To reduce the coupling, move the relevant variables as fields on structs that need them. Defining dependencies and configuration in a struct makes it easy. The use of interfaces is also quite helpful.</p> <h3 id="return-early-and-use-conditions-wisely">Return early and use conditions wisely</h3> <p>Conditional statements are something that we have to write a lot. It plays a massive role in whether the code is clean or messy. For example:</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">do</span><span class="p">(</span><span class="n">n</span> <span class="kt">int</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span> <span class="k">if</span> <span class="n">n</span> <span class="o">&gt;</span> <span class="m">12</span> <span class="p">{</span> <span class="k">return</span> <span class="no">false</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="no">true</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>The problem with this code is that the <code class="language-plaintext highlighter-rouge">else</code> statement is not helping here; rather, it makes the code messy and less readable. Instead, write it like:</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">do</span><span class="p">(</span><span class="n">n</span> <span class="kt">int</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span> <span class="k">if</span> <span class="n">n</span> <span class="o">&gt;</span> <span class="m">12</span> <span class="p">{</span> <span class="k">return</span> <span class="no">false</span> <span class="p">}</span> <span class="k">return</span> <span class="no">true</span> <span class="p">}</span> </code></pre></div></div> <p>We often see whole functions being wrapped in <code class="language-plaintext highlighter-rouge">if</code> statements like this:</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">do</span><span class="p">(</span><span class="n">n</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="n">n</span> <span class="o">&gt;</span> <span class="m">12</span> <span class="p">{</span> <span class="n">sum</span><span class="p">()</span> <span class="n">subtract</span><span class="p">()</span> <span class="n">multiply</span><span class="p">()</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Inverting conditional blocks will make it more clean and readable.</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">do</span><span class="p">(</span><span class="n">n</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="n">n</span> <span class="o">&lt;=</span> <span class="m">12</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span> <span class="n">sum</span><span class="p">()</span> <span class="n">subtract</span><span class="p">()</span> <span class="n">multiply</span><span class="p">()</span> <span class="p">}</span> </code></pre></div></div> <h3 id="use-switch-more-often">Use switch more often</h3> <p>The <strong>switch</strong> statement is the best way to keep the sequence of if-else statements shorter. <code class="language-plaintext highlighter-rouge">switch</code> is a beneficial feature for writing clean programs. The program often requires comparison, and our program uses too many <code class="language-plaintext highlighter-rouge">if-else</code>, making the code messy and less readable. So the use of switch helps a lot.</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">transact</span><span class="p">(</span><span class="n">bank</span> <span class="kt">string</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="n">bank</span> <span class="o">==</span> <span class="s">"Citi"</span> <span class="p">{</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"Tx #1: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bank</span><span class="p">)</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">bank</span> <span class="o">==</span> <span class="s">"StandardChartered"</span> <span class="p">{</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"Tx #2: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bank</span><span class="p">)</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">bank</span> <span class="o">==</span> <span class="s">"HSBC"</span> <span class="o">||</span> <span class="n">bank</span> <span class="o">==</span> <span class="s">"Deutsche"</span> <span class="o">||</span> <span class="n">bank</span> <span class="o">==</span> <span class="s">"JPMorgan"</span> <span class="p">{</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"Tx #3: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bank</span><span class="p">)</span> <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">bank</span> <span class="o">==</span> <span class="s">"NatWest"</span> <span class="p">{</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"Tx #4: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bank</span><span class="p">)</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"Tx #E: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bank</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>This looks messy, right? Now let’s use a <code class="language-plaintext highlighter-rouge">switch</code> instead. Here is how you can rewrite the same code in an idiomatic way:</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">transact</span><span class="p">(</span><span class="n">bank</span> <span class="kt">string</span><span class="p">)</span> <span class="p">{</span> <span class="k">switch</span> <span class="n">bank</span> <span class="p">{</span> <span class="k">case</span> <span class="s">"Citi"</span><span class="o">:</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"Tx #1: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bank</span><span class="p">)</span> <span class="k">case</span> <span class="s">"StandardChartered"</span><span class="o">:</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"Tx #2: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bank</span><span class="p">)</span> <span class="k">case</span> <span class="s">"HSBC"</span><span class="p">,</span> <span class="s">"Deutsche"</span><span class="p">,</span> <span class="s">"JPMorgan"</span><span class="o">:</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"Tx #3: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bank</span><span class="p">)</span> <span class="k">case</span> <span class="s">"NatWest"</span><span class="o">:</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"Tx #4: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bank</span><span class="p">)</span> <span class="k">default</span><span class="o">:</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"Tx #E: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">bank</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>In the future, if new banks get added, it will be easier and cleaner with <code class="language-plaintext highlighter-rouge">switch-case</code>.</p> <h3 id="continuous-code-refactoring">Continuous code refactoring</h3> <p>In large codebases, it’s essential to refactor the codebase structure. Code refactoring improves the readability and quality of code from time to time. It is not a one-time process; Teams should pay tech debts continuously to keep the codebase sane. I read somewhere nicely — “refactor early, refactor often”, which makes a lot of sense in writing maintainable Go code. Packages become heavy with time regarding the amount of code and responsibility, so it is better to break some packages into smaller packages as they are easy to maintain. Another good reason to refactor packages is to improve naming. It is also essential that a package only contains code related to its function. For example, Go moved from <code class="language-plaintext highlighter-rouge">os.SEEK_SET</code>, <code class="language-plaintext highlighter-rouge">os.SEEK_CUR</code>, and <code class="language-plaintext highlighter-rouge">os.SEEK_END</code> to <code class="language-plaintext highlighter-rouge">io.SeekStart</code>, <code class="language-plaintext highlighter-rouge">io.SeekCurrent</code>, and <code class="language-plaintext highlighter-rouge">io.SeekEnd</code> (respectively) are more readable. The package <code class="language-plaintext highlighter-rouge">io</code> is better for code involving <em>file I/O</em>. Breaking packages into small ones also makes the dependencies lightweight.</p> <h2 id="conclusion">Conclusion</h2> <p>With time and other programmers working on a codebase, we understand better what maintainability means. Writing maintainable code is not complicated; it requires knowledge, experience, and careful thought from everyone contributing to the code. The set of good practices that we discussed should help you and the team maintain your Go code better.</p> Wed, 12 Jan 2022 00:00:00 +0000 https://jogendra.dev/writing-maintainable-go-code https://jogendra.dev/writing-maintainable-go-code Import Cycles in Golang: How To Deal With Them <p>As a Golang developer, you probably have encountered import cycles. Golang do not allow import cycles. Go throws a compile-time error if it detects the import cycle in code. In this post, let’s understand how the import cycle occurs and how you can deal with them.</p> <h3 id="import-cycles">Import Cycles</h3> <p>Let’s say we have two packages, <code class="language-plaintext highlighter-rouge">p1</code> and <code class="language-plaintext highlighter-rouge">p2</code>. When package <code class="language-plaintext highlighter-rouge">p1</code> depends on package <code class="language-plaintext highlighter-rouge">p2</code> and package <code class="language-plaintext highlighter-rouge">p2</code> depends on package <code class="language-plaintext highlighter-rouge">p1</code>, it creates a cycle of dependency. Or it can be more complicated than this eg. package <code class="language-plaintext highlighter-rouge">p2</code> does not directly depend on package <code class="language-plaintext highlighter-rouge">p1</code> but <code class="language-plaintext highlighter-rouge">p2</code> depends on package <code class="language-plaintext highlighter-rouge">p3</code> which depends on <code class="language-plaintext highlighter-rouge">p1</code>, again it is cycle.</p> <p><img class="fullimg" alt="import cycle golang" src="https://user-images.githubusercontent.com/20956124/103145320-6c099f80-475e-11eb-8d24-9112b5e23dee.png" /></p> <p>Let’s understand it through some example code.</p> <p><em><strong>Package p1</strong>:</em></p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">p1</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"fmt"</span> <span class="s">"import-cycle-example/p2"</span> <span class="p">)</span> <span class="k">type</span> <span class="n">PP1</span> <span class="k">struct</span><span class="p">{}</span> <span class="k">func</span> <span class="n">New</span><span class="p">()</span> <span class="o">*</span><span class="n">PP1</span> <span class="p">{</span> <span class="k">return</span> <span class="o">&amp;</span><span class="n">PP1</span><span class="p">{}</span> <span class="p">}</span> <span class="k">func</span> <span class="p">(</span><span class="n">p</span> <span class="o">*</span><span class="n">PP1</span><span class="p">)</span> <span class="n">HelloFromP1</span><span class="p">()</span> <span class="p">{</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"Hello from package p1"</span><span class="p">)</span> <span class="p">}</span> <span class="k">func</span> <span class="p">(</span><span class="n">p</span> <span class="o">*</span><span class="n">PP1</span><span class="p">)</span> <span class="n">HelloFromP2Side</span><span class="p">()</span> <span class="p">{</span> <span class="n">pp2</span> <span class="o">:=</span> <span class="n">p2</span><span class="o">.</span><span class="n">New</span><span class="p">()</span> <span class="n">pp2</span><span class="o">.</span><span class="n">HelloFromP2</span><span class="p">()</span> <span class="p">}</span> </code></pre></div></div> <p><em><strong>Package p2</strong>:</em></p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">p2</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"fmt"</span> <span class="s">"import-cycle-example/p1"</span> <span class="p">)</span> <span class="k">type</span> <span class="n">PP2</span> <span class="k">struct</span><span class="p">{}</span> <span class="k">func</span> <span class="n">New</span><span class="p">()</span> <span class="o">*</span><span class="n">PP2</span> <span class="p">{</span> <span class="k">return</span> <span class="o">&amp;</span><span class="n">PP2</span><span class="p">{}</span> <span class="p">}</span> <span class="k">func</span> <span class="p">(</span><span class="n">p</span> <span class="o">*</span><span class="n">PP2</span><span class="p">)</span> <span class="n">HelloFromP2</span><span class="p">()</span> <span class="p">{</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"Hello from package p2"</span><span class="p">)</span> <span class="p">}</span> <span class="k">func</span> <span class="p">(</span><span class="n">p</span> <span class="o">*</span><span class="n">PP2</span><span class="p">)</span> <span class="n">HelloFromP1Side</span><span class="p">()</span> <span class="p">{</span> <span class="n">pp1</span> <span class="o">:=</span> <span class="n">p1</span><span class="o">.</span><span class="n">New</span><span class="p">()</span> <span class="n">pp1</span><span class="o">.</span><span class="n">HelloFromP1</span><span class="p">()</span> <span class="p">}</span> </code></pre></div></div> <p>On building, compiler returns the error:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>imports import-cycle-example/p1 imports import-cycle-example/p2 imports import-cycle-example/p1: import cycle not allowed </code></pre></div></div> <h3 id="import-cycles-are-bad-design">Import Cycles Are Bad Design</h3> <p>Go is highly focused on faster compile time rather than speed of execution (even willing to sacrifice some run-time performance for faster build). The Go compiler doesn’t spend a lot of time trying to generate the most efficient possible machine code, it cares more about compiling lots of code quickly.</p> <p>Allowing cyclic/circular dependencies would <strong>significantly increase compile times</strong> since the entire circle of dependencies would need to get recompiled every time one of the dependencies changed. It also increases the link-time cost and makes it hard to test/reuse things independently (more difficult to unit test because they cannot be tested in isolation from one another). Cyclic dependencies can result in infinite recursions sometimes.</p> <p>Cyclic dependencies may also cause memory leaks since each object holds on to the other, their reference counts will never reach zero and hence will never become candidates for collection and cleanup.</p> <p><a href="https://github.com/golang/go/issues/30247#issuecomment-463940936">Robe Pike, replying to proposal for allowing import cycles in Golang</a>, said that, this is one area where up-front simplicity is worthwhile. Import cycles can be convenient but their cost can be catastrophic. They should continue to be disallowed.</p> <h3 id="hammer_and_wrench-debugging-import-cycles">:hammer_and_wrench: Debugging Import Cycles</h3> <p>The worst thing about the import cycle error is, Golang doesn’t tell you source file or part of the code which is causing the error. So it becomes tough to figure out when the codebase is large. You would be wondering around different files/packages to check where actually the issue is. Why golang do not show the cause that causing the error? Because there is not only a single culprit source file in the cycle.</p> <p>But it does show the packages which are causing the issue. So you can look into those packages and fix the problem.</p> <p>To visualize the dependencies in your project, you can use <a href="https://github.com/kisielk/godepgraph">godepgraph</a>, a Go dependency graph visualization tool. It can be installed by running:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go get github.com/kisielk/godepgraph </code></pre></div></div> <p>It display graph in <a href="http://graphviz.org">Graphviz</a> dot format. If you have the graphviz tools installed (you can download from <a href="https://graphviz.org/download">here</a>), you can render it by piping the output to dot:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>godepgraph -s import-cycle-example | dot -Tpng -o godepgraph.png </code></pre></div></div> <p>You can see import cycle in output png file:</p> <p><img class="fullimg" alt="import cycle golang" style="width:25% !important;margin:unset" src="https://user-images.githubusercontent.com/20956124/103155444-0f41cf80-47c6-11eb-9ae8-791310e10b0a.png" /></p> <p>Apart from it, you can use <code class="language-plaintext highlighter-rouge">go list</code> to get some insights (run <code class="language-plaintext highlighter-rouge">go help list</code> for additional information).</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go list -f '{\{join .DepsErrors "\n"\}}' &lt;import-path&gt; </code></pre></div></div> <p>You can provide import path or can be left empty for current directory.</p> <h3 id="dealing-with-import-cycles">Dealing with Import Cycles</h3> <p>When you encounter the import cycle error, take a step back, and think about the project organization. The obvious and most common way to deal with the import cycle is implementation through interfaces. But sometimes you don’t need it. Sometimes you may have accidentally split your package into several. Check if packages that are creating import cycle are tightly coupled and they need each other to work, they probably should be merged into one package. In Golang, a package is a compilation unit. If two files must always be compiled together, they must be in the same package.</p> <h4 id="the-interface-way">The Interface Way:</h4> <ul> <li>Package <code class="language-plaintext highlighter-rouge">p1</code> to use functions/variables from package <code class="language-plaintext highlighter-rouge">p2</code> by importing package <code class="language-plaintext highlighter-rouge">p2</code>.</li> <li>Package <code class="language-plaintext highlighter-rouge">p2</code> to call functions/variables from package <code class="language-plaintext highlighter-rouge">p1</code> without having to import package <code class="language-plaintext highlighter-rouge">p1</code>. All it needs to be passed package <code class="language-plaintext highlighter-rouge">p1</code> instances which implement an interface defined in <code class="language-plaintext highlighter-rouge">p2</code>, those instances will be views as package <code class="language-plaintext highlighter-rouge">p2</code> object.</li> </ul> <p>That’s how package <code class="language-plaintext highlighter-rouge">p2</code> ignores the existence of package <code class="language-plaintext highlighter-rouge">p1</code> and import cycle is broken.</p> <p>After applying steps above, package <code class="language-plaintext highlighter-rouge">p2</code> code:</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">p2</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"fmt"</span> <span class="p">)</span> <span class="k">type</span> <span class="n">pp1</span> <span class="k">interface</span> <span class="p">{</span> <span class="n">HelloFromP1</span><span class="p">()</span> <span class="p">}</span> <span class="k">type</span> <span class="n">PP2</span> <span class="k">struct</span> <span class="p">{</span> <span class="n">PP1</span> <span class="n">pp1</span> <span class="p">}</span> <span class="k">func</span> <span class="n">New</span><span class="p">(</span><span class="n">pp1</span> <span class="n">pp1</span><span class="p">)</span> <span class="o">*</span><span class="n">PP2</span> <span class="p">{</span> <span class="k">return</span> <span class="o">&amp;</span><span class="n">PP2</span><span class="p">{</span> <span class="n">PP1</span><span class="o">:</span> <span class="n">pp1</span><span class="p">,</span> <span class="p">}</span> <span class="p">}</span> <span class="k">func</span> <span class="p">(</span><span class="n">p</span> <span class="o">*</span><span class="n">PP2</span><span class="p">)</span> <span class="n">HelloFromP2</span><span class="p">()</span> <span class="p">{</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"Hello from package p2"</span><span class="p">)</span> <span class="p">}</span> <span class="k">func</span> <span class="p">(</span><span class="n">p</span> <span class="o">*</span><span class="n">PP2</span><span class="p">)</span> <span class="n">HelloFromP1Side</span><span class="p">()</span> <span class="p">{</span> <span class="n">p</span><span class="o">.</span><span class="n">PP1</span><span class="o">.</span><span class="n">HelloFromP1</span><span class="p">()</span> <span class="p">}</span> </code></pre></div></div> <p>And package <code class="language-plaintext highlighter-rouge">p1</code> code look like:</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">p1</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"fmt"</span> <span class="s">"import-cycle-example/p2"</span> <span class="p">)</span> <span class="k">type</span> <span class="n">PP1</span> <span class="k">struct</span><span class="p">{}</span> <span class="k">func</span> <span class="n">New</span><span class="p">()</span> <span class="o">*</span><span class="n">PP1</span> <span class="p">{</span> <span class="k">return</span> <span class="o">&amp;</span><span class="n">PP1</span><span class="p">{}</span> <span class="p">}</span> <span class="k">func</span> <span class="p">(</span><span class="n">p</span> <span class="o">*</span><span class="n">PP1</span><span class="p">)</span> <span class="n">HelloFromP1</span><span class="p">()</span> <span class="p">{</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"Hello from package p1"</span><span class="p">)</span> <span class="p">}</span> <span class="k">func</span> <span class="p">(</span><span class="n">p</span> <span class="o">*</span><span class="n">PP1</span><span class="p">)</span> <span class="n">HelloFromP2Side</span><span class="p">()</span> <span class="p">{</span> <span class="n">pp2</span> <span class="o">:=</span> <span class="n">p2</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="n">pp2</span><span class="o">.</span><span class="n">HelloFromP2</span><span class="p">()</span> <span class="p">}</span> </code></pre></div></div> <p>You can use this code in <code class="language-plaintext highlighter-rouge">main</code> package to test.</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"import-cycle-example/p1"</span> <span class="p">)</span> <span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span> <span class="n">pp1</span> <span class="o">:=</span> <span class="n">p1</span><span class="o">.</span><span class="n">PP1</span><span class="p">{}</span> <span class="n">pp1</span><span class="o">.</span><span class="n">HelloFromP2Side</span><span class="p">()</span> <span class="c">// Prints: "Hello from package p2"</span> <span class="p">}</span> </code></pre></div></div> <p>You can find full source code on GitHub at <a href="https://github.com/jogendra/import-cycle-example-go"><strong>jogendra/import-cycle-example-go</strong></a></p> <p><strong>Other way</strong> of using the interface to break cycle can be extracting code into separate 3rd package that act as bridge between two packages. But many times it increases code repetition. You can go for this approach keeping your code structure in mind.</p> <blockquote> <p><strong>“Three Way”</strong> Import Chain: Package p1 -&gt; Package m1 &amp; Package p2 -&gt; Package m1</p> </blockquote> <h4 id="the-ugly-way">The Ugly Way:</h4> <p>Interestingly, you can avoid importing package by making use of <code class="language-plaintext highlighter-rouge">go:linkname</code>. <code class="language-plaintext highlighter-rouge">go:linkname</code> is compiler directive (used as <code class="language-plaintext highlighter-rouge">//go:linkname localname [importpath.name]</code>). This special directive does not apply to the Go code that follows it. Instead, the <em>//go:linkname</em> directive instructs the compiler to use <em>“importpath.name”</em> as the object file symbol name for the variable or function declared as <em>“localname”</em> in the source code. (definition from <a href="https://golang.org/cmd/compile/#hdr-Compiler_Directives">golang.org</a>, hard to understand at first sight, look at the source code link below, I tried solving import cycle using it.)</p> <p>There are many Go standard package rely on runtime private calls using <code class="language-plaintext highlighter-rouge">go:linkname</code>. You can also solve import cycle in your code sometime with it but you should avoid using it as it is still a hack and not recommended by the Golang team.</p> <p><strong>Point to note</strong> here is Golang standard package <strong>do not</strong> use <code class="language-plaintext highlighter-rouge">go:linkname</code> to avoid import cycle, rather they use it to avoid exporting APIs that shouldn’t be public.</p> <p>Here is the source code of solution which I implemented using <code class="language-plaintext highlighter-rouge">go:linkname</code> :</p> <p><strong>-&gt;</strong> <a href="https://github.com/jogendra/import-cycle-example-go/tree/golinkname">jogendra/import-cycle-example-go -&gt; golinkname</a></p> <h3 id="bottom-lines">Bottom Lines</h3> <p>The import cycle is definitely a pain when the codebase is large. Try to build the application in layers. The higher-level layer should import lower layers but lower layers should not import higher layer (it create cycle). Keeping this in mind and sometimes merging tightly coupled packages into one is a good solution than solving through interfaces. But for more generic cases, interface implementation is a good way to break the import cycles.</p> <p>You can checkout interesting <a href="https://www.reddit.com/r/golang/comments/kphblv/import_cycles_in_golang_and_how_to_deal_with_them">discussion about this blog post on Reddit here</a>.</p> <p>I hope you got a fair understanding of Import Cycles. Do share and reach out to me on <a href="https://twitter.com/jogendrafx">Twitter</a> in case of anything. You can follow my open source work at <a href="https://github.com/jogendra">github/jogendra</a>. Thanks for read :)</p> Sun, 03 Jan 2021 00:00:00 +0000 https://jogendra.dev/import-cycles-in-golang-and-how-to-deal-with-them https://jogendra.dev/import-cycles-in-golang-and-how-to-deal-with-them Implementing Launch at Login Feature in MacOS Apps <p>In this post, I will explain, how you can add the Launch at Login (launch application on system startup) feature to your MacOS Applications. Adding this feature is way more complex than you would expect. I use <a href="https://github.com/leits/MeetingBar">MeetingBar</a> for managing my work/personal meetings over Google Meet and Zoom. MeetingBar is an open-source MacOS App. Recently, I added the Launch at Login feature to MeetingBar. Initially, I thought that there will be a simple solution to it and it will take minutes only. But when I started implementing it, I found out that it is not really that straight forward. You can find the implementation source code below.</p> <p>Link to Pull Request: <a href="https://github.com/leits/MeetingBar/pull/76">Add Launch at Login feature</a></p> <p>Source Code for this post: <a href="https://github.com/jogendra/launch-at-login-macos">https://github.com/jogendra/launch-at-login-macos</a></p> <p>For the users, a straightforward solution is going to system preference and enable auto launching for applications (<em>Preferences -&gt; Users &amp; Groups -&gt; Login Items</em>). But it is always good if the application itself offers the feature from its preference. For application developers, it’s a bit tricky.</p> <p>There are a couple of ways to add a login item to the application. The most used/useful way is using the <a href="https://developer.apple.com/documentation/servicemanagement">Service Management framework</a>. Login items installed using the Service Management framework are not visible in System Preferences and can only be removed by the application that installed them. The other way is using a shared file list. Login items installed using a shared file list are visible in System Preferences; users have direct control over them. If you use this API, your login item can be disabled by the user.</p> <p>In this post, I will explain how to add login items to MacOS Applications using the Service Management framework. The idea is to create an application inside the main application that handles the auto launching for the main application.</p> <h2 id="little-bit-about-apple-app-sandboxing">Little bit about Apple App Sandboxing</h2> <p>According to Apple documentation, to distribute a macOS app through the Mac App Store, you must enable the App Sandbox capability. App Sandbox provides protection to system resources and user data by limiting your app’s access to resources requested through entitlements. Apps signed and distributed outside of the Mac App Store with Developer ID can (and in most cases should) use App Sandbox as well. App Sandbox enables you to describe how your app interacts with the system. The system then grants your app the access it needs to get its job done and no more. It is always best to design your apps with App Sandbox in mind. You can check out this detailed <a href="https://www.appcoda.com/mac-app-sandbox/">article by Appcoda on Mac App Sandbox</a>.</p> <p>Every application is given a sandbox, a directory it can use to store data. An application cannot access the sandbox of another application. If the application needs access to data on the device that isn’t located in the application’s sandbox, it needs to request the data through a system interface. System interfaces add a layer of security and protect the user’s privacy.</p> <p><img class="fullimg" alt="apple app sandboxing" src="https://user-images.githubusercontent.com/20956124/93572750-ea161780-f9b3-11ea-8f32-e94a6b5a2672.png" /></p> <p>To adopt App Sandbox for a target in an Xcode project, apply the <code class="language-plaintext highlighter-rouge">&lt;true/&gt;</code> value to the <code class="language-plaintext highlighter-rouge">com.apple.security.app-sandbox</code> entitlement key for that target. Do this in the Xcode target editor by selecting the Enable App Sandboxing checkbox.</p> <!-- ```xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.app-sandbox</key> <true/> </dict> </plist> ``` --> <p><img class="fullimg" alt="entitlement" src="https://user-images.githubusercontent.com/20956124/93665547-f45c1280-fa94-11ea-96d4-7dbdabd83172.png" /></p> <p>Generally, when you create macOS App, it is already added, you don’t have to worry about it.</p> <h2 id="zap-initial-setups">:zap: Initial Setups</h2> <p>In this post, we will use <strong>MainApplication</strong> for the main application (using SwiftUI) in which we are going to add Launch at Login functionality and the other one will be <strong>AutoLauncher</strong>, which is our <em>helper application</em> that is responsible for registering our main application for login items.</p> <p><img class="fullimg" alt="main application general settings" src="https://user-images.githubusercontent.com/20956124/93586575-218dbf80-f9c6-11ea-8a93-af7699914094.png" /></p> <h2 id="hammer_and_wrench-adding-helper-target">:hammer_and_wrench: Adding Helper Target</h2> <p>To add helper target AutoLauncher to our MainApplication, go to <em>File -&gt; New -&gt; Target</em> and choose <strong>macOS</strong> template with <strong>App</strong> selected (under <em>Application</em> section) and click <code class="language-plaintext highlighter-rouge">Next</code> button to choose options for helper application.</p> <p><img class="diffimg" alt="select target templete" src="https://user-images.githubusercontent.com/20956124/93664146-62e7a300-fa8a-11ea-9199-086f3674c806.png" /></p> <p>In next screen, choose <em>‘AutoLauncher’</em> for <code class="language-plaintext highlighter-rouge">Product Name</code> (or whatever name you want to give), select <em>‘Storyboard’</em> option for the <code class="language-plaintext highlighter-rouge">User Interface</code> option (we will delete this interface later anyway), make sure the <code class="language-plaintext highlighter-rouge">Project</code> option has <em>‘MainApplication’</em> target selected and finish the setup.</p> <!-- <img class="fullimg" alt="select project options" src="https://user-images.githubusercontent.com/20956124/93589295-3d936000-f9ca-11ea-804e-31cc262c8b0c.png"> --> <h2 id="gear-helper-target-configurations">:gear: Helper Target Configurations</h2> <ul> <li>Since our helper target AutoLauncher should be background only as we don’t need user interface for AutoLauncher. So to make it background only service, go to <code class="language-plaintext highlighter-rouge">Info.plist</code> of AutoLauncher, set <code class="language-plaintext highlighter-rouge">Application is background only</code> key to <code class="language-plaintext highlighter-rouge">YES</code>.</li> </ul> <!-- <img class="fullimg" alt="AutoLauncher Info.plist" src="https://user-images.githubusercontent.com/20956124/93661127-f3b28480-fa72-11ea-8e74-b734d6ccd814.png"> --> <p><img class="fullimg" alt="AutoLauncher Info.plist" src="https://user-images.githubusercontent.com/20956124/93661035-36279180-fa72-11ea-8350-5e58078d83a9.png" /></p> <ul> <li>Go to <em>Targets</em>, select <em>‘AutoLauncher’</em> Target’s Build Settings, and search for <em>“skip install”</em>. Set <code class="language-plaintext highlighter-rouge">Skip Install</code> to <code class="language-plaintext highlighter-rouge">Yes</code>. Otherwise we would end up with too many bundles in our final app archive.</li> </ul> <p><img class="fullimg" alt="Skip Install Settings" src="https://user-images.githubusercontent.com/20956124/93661621-be0f9a80-fa76-11ea-9319-7953df61180c.png" /></p> <ul> <li>Go to <code class="language-plaintext highlighter-rouge">AppDelegate.swift</code> of AutoLauncher, <strong>rename</strong> class name <code class="language-plaintext highlighter-rouge">AppDelegate</code> to <code class="language-plaintext highlighter-rouge">AutoLauncherAppDelegate</code> (or whatever name you want to give) because it is conflicting with <code class="language-plaintext highlighter-rouge">AppDelegate</code> class of our MainApplication. We will configure this Delegate class method <code class="language-plaintext highlighter-rouge">applicationDidFinishLaunching(_ :)</code> like below. It check if MainApplication is running, if not, launch application using <a href="https://developer.apple.com/documentation/appkit/nsworkspace/3172700-openapplication"><code class="language-plaintext highlighter-rouge">openApplication(at:_:_:)</code></a>. This instance method launches the app at the specified path URL.</li> </ul> <!-- ```swift import Cocoa @NSApplicationMain class AutoLauncherAppDelegate: NSObject, NSApplicationDelegate { struct Constants { // Bundle Identifier of MainApplication target static let mainAppBundleID = "com.jogendra.MainApplication" } func applicationDidFinishLaunching(_ aNotification: Notification) { let runningApps = NSWorkspace.shared.runningApplications let isRunning = runningApps.contains { $0.bundleIdentifier == Constants.mainAppBundleID } if !isRunning { var path = Bundle.main.bundlePath as NSString for _ in 1...4 { path = path.deletingLastPathComponent as NSString } let applicationPathString = path as String guard let pathURL = URL(string: applicationPathString) else { return } NSWorkspace.shared.openApplication(at: pathURL, configuration: NSWorkspace.OpenConfiguration(), completionHandler: nil) } } } ``` --> <p><img class="fullimg" alt="App delegate code" src="https://user-images.githubusercontent.com/20956124/93665403-cfb36b00-fa93-11ea-9655-01a2a441e553.png" /></p> <p>This AutoLauncher app target is actually embedded inside the MainApplication bundle under the subdirectory <code class="language-plaintext highlighter-rouge">Contents/Library/LoginItems</code> (we are going to do this later). So including the helper app target name (AutoLauncher) there will be a total of <strong>4 path components</strong> to be deleted. That’s why we are running loop 4 times.</p> <h2 id="anger-cleaning-up-helper-target-ui-elements">:anger: Cleaning Up Helper Target UI Elements</h2> <p>Helper target app (AutoLauncher) should not have any UI elements. We will remove all the visual elements from the app, even the <em>Main.storyboard</em>. Now, got ahead and:</p> <ul> <li>Delete <code class="language-plaintext highlighter-rouge">ViewController.swift</code> file.</li> <li>Delete <code class="language-plaintext highlighter-rouge">Assets.xcassets</code> folder.</li> <li>Delete <code class="language-plaintext highlighter-rouge">Main.storyboard</code>.</li> </ul> <p>Since we have deleted entry point of AutoLauncher target which was earlier configured in deleted <code class="language-plaintext highlighter-rouge">Main.storyboard</code> file. To make that up, follow:</p> <ul> <li>Create and add <code class="language-plaintext highlighter-rouge">main.swift</code> file (please note it has to be <em>”main”</em> with lowercased <em>”m”</em>, otherwise it won’t work) to AutoLauncher.</li> <li>Remove <code class="language-plaintext highlighter-rouge">@NSApplicationMain</code> from <code class="language-plaintext highlighter-rouge">AutoLauncherAppDelegate</code> class. <code class="language-plaintext highlighter-rouge">@NSApplicationMain</code> is no longer needed because in-general <code class="language-plaintext highlighter-rouge">@NSApplicationMain</code> implicit generate the <code class="language-plaintext highlighter-rouge">main.swift</code> file for us which we have already created.</li> <li>Set the Application delegate like below in <code class="language-plaintext highlighter-rouge">main.swift</code>:</li> </ul> <p><img class="fullimg" alt="main.swift code" src="https://user-images.githubusercontent.com/20956124/93665199-40f21e80-fa92-11ea-97d2-a890926ce06c.png" /></p> <p>Clean? We are done with all the setups for helper. Now, let’s configure our Main Application.</p> <h2 id="electric_plug-adding-helper-to-main-application">:electric_plug: Adding Helper to Main Application</h2> <p>We have to copy the helper application AutoLauncher inside our MainApplication. To do so, go to Project Setting, have <em><code class="language-plaintext highlighter-rouge">MainApplication</code></em> target selected, select <em>Build Phases</em>, click <strong><code class="language-plaintext highlighter-rouge">+</code></strong> icon shown below, and select <code class="language-plaintext highlighter-rouge">New Copy Files Phase</code>.</p> <p><img class="fullimg" alt="New Copy Files Phase" src="https://user-images.githubusercontent.com/20956124/93669587-e1583b00-fab2-11ea-9c78-a947854cd756.png" /></p> <p>Apply Settings as below:</p> <p>Select <strong>Wrapper</strong> as <code class="language-plaintext highlighter-rouge">Destination</code>, put <strong><em>Contents/Library/LoginItems</em></strong> in <code class="language-plaintext highlighter-rouge">Subpath</code> and finally click on <strong>+</strong> icon to add <code class="language-plaintext highlighter-rouge">AutoLauncher.app</code> there (it will be inside <em>Product</em> folder).</p> <p><img class="fullimg" alt="New Copy Files Phase" src="https://user-images.githubusercontent.com/20956124/93670076-07330f00-fab6-11ea-9160-2b257b0a0411.png" /></p> <p>Now, go to <code class="language-plaintext highlighter-rouge">General</code> tab and Add <strong>ServiceManagement</strong> framework to <code class="language-plaintext highlighter-rouge">Frameworks, Libraries, and Embedded Content</code> section.</p> <p>You can now import <code class="language-plaintext highlighter-rouge">ServiceManagement</code> to any of your swift file and use the <a href="https://developer.apple.com/documentation/servicemanagement/1501557-smloginitemsetenabled?language=swift"><strong><code class="language-plaintext highlighter-rouge">SMLoginItemSetEnabled(_:_:)</code></strong></a> function to enable a helper application. It takes two arguments, a <code class="language-plaintext highlighter-rouge">CFStringRef</code> containing the bundle identifier of the helper application, and a <code class="language-plaintext highlighter-rouge">Boolean</code> specifying the desired state. This value is effective only for the currently logged-in user. Pass <strong><code class="language-plaintext highlighter-rouge">true</code></strong> to start the helper application immediately and indicate that it should be started every time the user logs in. Pass <strong><code class="language-plaintext highlighter-rouge">false</code></strong> to terminate the helper application and indicate that it should no longer be launched when the user logs in. This function returns true if the requested change has taken effect; otherwise, it returns false. This function can be used to manage any number of helper applications.</p> <h4 id="example-ui">Example UI</h4> <p><img class="diffimg" alt="Example UI for Launch at Login" src="https://user-images.githubusercontent.com/20956124/93679403-02c52100-facb-11ea-898b-36989d11d03e.png" style="width:200px !important" /></p> <p>In this example, I just added a simple toggle using SwiftUI. On toggle, it’s calling <code class="language-plaintext highlighter-rouge">SMLoginItemSetEnabled</code> function with helper app identifier and current state of toggle. Further, you can store current state in UserDefaults.</p> <p><img class="fullimg" alt="Example UI for Launch at Login" src="https://user-images.githubusercontent.com/20956124/93672414-4b2f0f80-fac8-11ea-968e-5f80bf6f1241.png" /></p> <p><strong>NOTE</strong>: If multiple applications (for example, several applications from the same organization) contain a helper application with the same bundle identifier, only the one with the greatest bundle version number is launched. Any of the applications that contain a copy of the helper application can enable and disable it. You should keep this in mind if you are shipping applications under the same organization. You can read more in detail about this <a href="https://www.appcoda.com/app-group-macos-ios-communication/">here</a>.</p> <h3 id="link-helpful-links">:link: Helpful Links</h3> <ol> <li><a href="https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW4">App Sandbox in Depth</a></li> <li><a href="https://developer.apple.com/tutorials/swiftui/creating-a-macos-app">Creating a macOS App in SwiftUI</a></li> <li><a href="https://developer.apple.com/documentation/servicemanagement/1501557-smloginitemsetenabled?language=swift">SMLoginItemSetEnabled Method Documentations</a></li> <li><a href="https://www.appcoda.com/mac-app-sandbox/">Beyond App Sandbox: Going outside of the macOS app container</a></li> <li><a href="https://www.appcoda.com/app-group-macos-ios-communication/">Using App Groups for communication between macOS/iOS apps from the Same Vendor</a></li> </ol> <h3 id="memo-closing-notes">:memo: Closing Notes</h3> <p>We have seen how we can add login items to any MacOS application. You can <a href="https://github.com/jogendra/launch-at-login-macos"><strong>Download Full Source Code Here</strong></a>. Adding login items to macOS applications isn’t that straight-forward. You need to follow all the setps carefully. I hope this post helped you in some way. Do share and <a href="https://twitter.com/jogendrafx">drop me suggestions/questions on Twitter</a>. Thanks for read :)</p> Sun, 20 Sep 2020 00:00:00 +0000 https://jogendra.dev/implementing-launch-at-login-feature-in-macos-apps https://jogendra.dev/implementing-launch-at-login-feature-in-macos-apps Using Vim for Go Development <p>As a programmer, you spend a lot of time using text editors, IDEs, etc to code. So it is important what and how you use it. I recently moved to Vim for Go development and I am totally loving it. Initially, I was using VSCode with Go extension and after that, I used GoLand for a while. <a href="https://github.com/microsoft/vscode">Visual Studio Code</a> and <a href="https://www.jetbrains.com/go/">GoLand</a> both are totally awesome, I loved using them too. It definitely takes some time to learn using vim but it is worth the learning. It is totally up to you what works better for you. Do not use vim just because others are using it. I am very much a beginner to vim and things I am going to explain in this is what I learned after I started using it. I would be really glad if you are a long time Vim user and have some good advice/suggestions. You can put them in the comment section of this post. If you are a beginner, I will put some good resources for learning vim at the bottom of this post.</p> <h2 id="why-do-people-use-vim">Why do people use Vim?</h2> <ul> <li>Vim is guaranteed to exist on all Unix systems (basically, it’s available on every major platform). It’s everywhere, and it’s the same on all platforms. Vim supports all file formats and programming languages, no need to download different IDEs for different programming languages.</li> <li>Mouse free navigation. Your fingers very rarely have to leave the home row, which means you’ll be able to edit text very, very quickly. You can do key mapping according to your convenience. Not having to switch between keyboard and mouse is definitely nice with Vim.</li> <li>Vim is very light-weight and fast. Vim can easily handle large files but Visual Studio or other IDE couldn’t handle them smoothly. Try opening a large file (~20MB) with your favorite non-vim text editor and vim, you will definitely get to know the difference. There is great power in the way Vim handles files (buffers), splits (windows), and layouts (tabs), the power that you can’t get in other editors.</li> <li>Vim and vim like text editors (emacs, nano, etc) feel and look so native, no distraction. I don’t have to switch between the terminal and editor/IDE. It’s just simply everything in one place. It’s pretty easy to be productive in vim with little effort.</li> <li>Vim is very handy when you have to make quick configurational or other changes to the remote machine/server. Even on your own machine, you won’t open a heavy text editor for small change.</li> <li>Vim is highly configurable. You can simply use <code class="language-plaintext highlighter-rouge">~/.vimrc</code> to override default configurations and personalize yours. Vim plugins provide everything to vim that you need in a text editor. Autocomplete, syntax highlighting, or any other feature you name it and it is available, you just need the right plug-in.</li> <li>Using vim is fun. It’s little challenging to use initially, but it become fun once you get used to it.</li> </ul> <h2 id="lets-go">Let’s GO!</h2> <p>In this post, we will explore setting up Vim as a full-fledged IDE for Go development. Plugins we are going to use are:</p> <ul> <li><a href="https://github.com/fatih/vim-go">vim-go</a>: Go development plugin for Vim. It has everything you need for Go development.</li> <li><a href="https://github.com/preservim/nerdtree">NERDTree</a> - File system explorer for the Vim. It enables you to visually browse complex directory hierarchies, quickly open files for reading or editing, and perform basic file system operations.</li> </ul> <h3 id="electric_plug-vim-go">:electric_plug: <a href="https://github.com/fatih/vim-go">Vim-Go</a></h3> <p>Vim-Go is most popular and most used vim plugin for Go development. It offer almost all the features you may need. Some of them are:</p> <ul> <li>Compile your package with <code class="language-plaintext highlighter-rouge">:GoBuild</code>, install it with <code class="language-plaintext highlighter-rouge">:GoInstall</code> or test it with <code class="language-plaintext highlighter-rouge">:GoTest</code>.</li> <li>Quickly execute your current file(s) with <code class="language-plaintext highlighter-rouge">:GoRun</code>.</li> <li>Syntax highlighting and folding.</li> <li>Autocompletion support via <code class="language-plaintext highlighter-rouge">gopls</code>.</li> <li>Run <code class="language-plaintext highlighter-rouge">gofmt</code> and <code class="language-plaintext highlighter-rouge">goimports</code> on save. Means, you don’t have to worry about importing packages and code formatting, vim-go will do that for you on saving the file.</li> <li>Go to symbol/declaration with <code class="language-plaintext highlighter-rouge">:GoDef</code> and look up documentation with <code class="language-plaintext highlighter-rouge">:GoDoc</code> or <code class="language-plaintext highlighter-rouge">:GoDocBrowser</code>.</li> <li>Check code coverage, do code linting etc.</li> </ul> <p>You can check all the feature offered by vim-go <a href="https://github.com/fatih/vim-go#features">here</a>.</p> <h4 id="installing-vim-go">Installing vim-go</h4> <p>Vim 8 added native support for packages which mean you can load plugins by just cloning their contents into your <code class="language-plaintext highlighter-rouge">~/.vim</code> directory. To install vim-go, you can simple run command:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/fatih/vim-go.git ~/.vim/pack/plugins/start/vim-go </code></pre></div></div> <p>You will also need to install all the necessary binaries. vim-go makes it easy to install all of them by providing a command, <code class="language-plaintext highlighter-rouge">:GoInstallBinaries</code>, which will <code class="language-plaintext highlighter-rouge">go get</code> all the required binaries.</p> <p>Or you can put plugin in your <code class="language-plaintext highlighter-rouge">~/.vimrc</code>.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>call plug#begin<span class="o">()</span> Plug <span class="s1">'fatih/vim-go'</span>, <span class="o">{</span> <span class="s1">'do'</span>: <span class="s1">':GoUpdateBinaries'</span> <span class="o">}</span> call plug#end<span class="o">()</span> </code></pre></div></div> <p>Now save the <code class="language-plaintext highlighter-rouge">.vimrc</code> and install the plugin by running <code class="language-plaintext highlighter-rouge">vim +PlugInstall</code>. It will install vim-go plugin with all the necessary binaries.</p> <p>I have added following vim-go related directives to my <code class="language-plaintext highlighter-rouge">.vimrc</code>, to make it more useful:</p> <div class="language-viml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">filetype</span> plugin <span class="nb">indent</span> <span class="k">on</span> <span class="k">set</span> <span class="nb">autowrite</span> “ Go <span class="nb">syntax</span> highlighting <span class="k">let</span> <span class="nv">g:go_highlight_fields</span> <span class="p">=</span> <span class="m">1</span> <span class="k">let</span> <span class="nv">g:go_highlight_functions</span> <span class="p">=</span> <span class="m">1</span> <span class="k">let</span> <span class="nv">g:go_highlight_function_calls</span> <span class="p">=</span> <span class="m">1</span> <span class="k">let</span> <span class="nv">g:go_highlight_extra_types</span> <span class="p">=</span> <span class="m">1</span> <span class="k">let</span> <span class="nv">g:go_highlight_operators</span> <span class="p">=</span> <span class="m">1</span> “ Auto formatting <span class="nb">and</span> importing <span class="k">let</span> <span class="nv">g:go_fmt_autosave</span> <span class="p">=</span> <span class="m">1</span> <span class="k">let</span> <span class="nv">g:go_fmt_command</span> <span class="p">=</span> <span class="s2">"goimports"</span> <span class="c">" Status line types/signatures</span> <span class="k">let</span> <span class="nv">g:go_auto_type_info</span> <span class="p">=</span> <span class="m">1</span> <span class="c">" Run :GoBuild or :GoTestCompile based on the go file</span> <span class="k">function</span><span class="p">!</span> <span class="nv">s:build_go_files</span><span class="p">()</span> <span class="k">let</span> <span class="k">l</span><span class="p">:</span><span class="k">file</span> <span class="p">=</span> <span class="nb">expand</span><span class="p">(</span><span class="s1">'%'</span><span class="p">)</span> <span class="k">if</span> <span class="k">l</span><span class="p">:</span><span class="k">file</span> <span class="p">=~</span># <span class="s1">'^\f\+_test\.go$'</span> <span class="k">call</span> <span class="k">go</span>#test#Test<span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">1</span><span class="p">)</span> <span class="k">elseif</span> <span class="k">l</span><span class="p">:</span><span class="k">file</span> <span class="p">=~</span># <span class="s1">'^\f\+\.go$'</span> <span class="k">call</span> <span class="k">go</span>#cmd#Build<span class="p">(</span><span class="m">0</span><span class="p">)</span> <span class="k">endif</span> <span class="k">endfunction</span> “ Map <span class="nb">keys</span> <span class="k">for</span> most used commands<span class="p">.</span> “ Ex<span class="p">:</span> `\<span class="k">b</span>` <span class="k">for</span> building<span class="p">,</span> `\<span class="k">r</span>` <span class="k">for</span> running <span class="nb">and</span> `\<span class="k">b</span>` <span class="k">for</span> running test<span class="p">.</span> autocmd <span class="nb">FileType</span> <span class="k">go</span> nmap <span class="p">&lt;</span>leader<span class="p">&gt;</span><span class="k">b</span> <span class="p">:&lt;</span>C<span class="p">-</span><span class="k">u</span><span class="p">&gt;</span><span class="k">call</span> <span class="p">&lt;</span>SID<span class="p">&gt;</span>build_go_files<span class="p">()&lt;</span>CR<span class="p">&gt;</span> autocmd <span class="nb">FileType</span> <span class="k">go</span> nmap <span class="p">&lt;</span>leader<span class="p">&gt;</span><span class="k">r</span> <span class="p">&lt;</span>Plug<span class="p">&gt;(</span><span class="k">go</span><span class="p">-</span>run<span class="p">)</span> autocmd <span class="nb">FileType</span> <span class="k">go</span> nmap <span class="p">&lt;</span>leader<span class="p">&gt;</span><span class="k">t</span> <span class="p">&lt;</span>Plug<span class="p">&gt;(</span><span class="k">go</span><span class="p">-</span>test<span class="p">)</span> </code></pre></div></div> <p>You can visit <a href="https://github.com/fatih/vim-go/wiki/Tutorial">official tutorials</a> for your own customisations. Complete vim-go documentations are available in your vim editor, you can simply run <code class="language-plaintext highlighter-rouge">:help vim-go</code> to check them out. This is how my vim look for a Go programm after installing and configuring vim-go:</p> <p><img class="fullimg" alt="go program with vim-go" src="https://user-images.githubusercontent.com/20956124/88539500-fd99a780-d02e-11ea-91cf-97ce4c10ccc6.png" /></p> <p>Beautiful?</p> <h4 id="auto-completion">Auto Completion</h4> <p>It’s really hard to code without auto completion and not being able to going to definition. Vim-go can use <code class="language-plaintext highlighter-rouge">gopls</code> for completion etc. Completion is enabled by default via <strong>omnifunc</strong>. While you are in <code class="language-plaintext highlighter-rouge">INSERT</code> mode, you can trigger completion with <code class="language-plaintext highlighter-rouge">Ctrl-x Ctrl-p</code>. If matching names are found, a pop-up menu opens which can be navigated using the <code class="language-plaintext highlighter-rouge">Ctrl-n</code> and <code class="language-plaintext highlighter-rouge">Ctrl-p</code> keys. To check if vim-go’s <code class="language-plaintext highlighter-rouge">ftplugin/go.vim</code> has been configured correctly for autocompletion, you can run:</p> <div class="language-viml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">:</span><span class="k">verbose</span> <span class="k">setlocal</span> <span class="nb">omnifunc</span>? </code></pre></div></div> <p>Message below should appear:</p> <div class="language-viml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">omnifunc</span><span class="p">=</span><span class="k">go</span>#<span class="nb">complete</span>#Complete Last <span class="k">set</span> from <span class="p">~</span><span class="sr">/.vim/</span>pack<span class="sr">/plugins/</span>start<span class="sr">/vim-go/</span>ftplugin/<span class="k">go</span><span class="p">.</span><span class="k">vim</span> </code></pre></div></div> <p>If you want the autocomplete prompt to appear automatically whenever you press the <em>dot</em> (<code class="language-plaintext highlighter-rouge">.</code>), you can add the following line to your <code class="language-plaintext highlighter-rouge">.vimrc</code>:</p> <div class="language-viml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">au</span> <span class="k">filetype</span> <span class="k">go</span> inoremap <span class="p">&lt;</span><span class="k">buffer</span><span class="p">&gt;</span> <span class="p">.</span> <span class="p">.&lt;</span>C<span class="p">-</span><span class="k">x</span><span class="p">&gt;&lt;</span>C<span class="p">-</span><span class="k">o</span><span class="p">&gt;</span> </code></pre></div></div> <p>Now, typing should offer auto suggestions along with documentation previews. It should look something like this:</p> <p><img class="fullimg" alt="coc autocompletion screenshot" src="https://user-images.githubusercontent.com/20956124/88567355-feddcb00-d054-11ea-9ea1-d556d1e13e1c.png" /></p> <p>Apart from vim-go completion, there some good plugins which you can use for auto completion and other LSP features. Some of them are:</p> <ul> <li><a href="https://github.com/neoclide/coc.nvim">coc.nvim (Conquer of Completion)</a>: Intellisense engine for Vim8 &amp; Neovim, full language server protocol support as VSCode.</li> <li><a href="https://github.com/autozimu/LanguageClient-neovim">LanguageClient-neovim</a>: Minimal Language Server Protocol client written in Rust.</li> <li><a href="https://github.com/ycm-core/YouCompleteMe">YouCompleteMe</a>: Code-completion engine. It has several completion engines.</li> </ul> <h4 id="language-server-protocol-lsp-and-gopls">Language Server Protocol (LSP) and gopls</h4> <p>The Language Server protocol is used between a tool (the client) and a language smartness provider (the server) to integrate features like auto complete, go to definition, find all references and alike into the tool. The goal of the protocol is to allow programming language support to be implemented and distributed independently of any given editor or IDE. The Language Server Protocol was originally developed for Microsoft’s Visual Studio Code and is now an open standard. Microsoft collaborated with Red Hat and Codenvy to standardise the protocol’s specification.</p> <p>Before LSP, there used to be different IDEs for different languages (still there are many). Every IDEs had language specific integration (syntax highliting, auto completion, going to defination, etc features). The situation is often referred to as an M × N problem, where the number of integrations is the product of M editors and N languages. What the Language Server Protocol does is change this M × N problem into a M + N problem. Best things about LSP is, it is totally independent of IDEs. As a result we can see that, Visual Studio work for so many different languages via extensions. These extensions make use of LSP.</p> <p>You can read more about LSP <a href="https://microsoft.github.io/language-server-protocol/">here</a> and <a href="https://docs.microsoft.com/en-us/visualstudio/extensibility/language-server-protocol?view=vs-2019">her</a> or watch this <a href="https://www.youtube.com/watch?v=2GqpdfIAhz8">YouTube video</a> to see how it works.</p> <p>Official Language Server for Golang is <a href="https://github.com/golang/tools/blob/master/gopls/README.md"><strong>gopls</strong></a> (pronounced: <em>”go please”</em>). You can check more information about the <a href="https://github.com/golang/tools/blob/master/gopls/doc/status.md">status of gopls and its supported features</a>.</p> <h3 id="electric_plug-nerdtree">:electric_plug: <a href="https://github.com/preservim/nerdtree">NERDTree</a></h3> <p>After setting up a lot of stuffs in your Vim, one thing you probably still missing the most is <em>“Side Window”</em> where you can browse your project files easily. NERDTree got you there. NERDTree is a fully fledged file system explorer, which helps to visually browse your files using a tree-style side window. With NERDTree, you can quickly open files for reading or editing, and perform basic file system operations.</p> <h4 id="installation-and-setups">Installation and Setups</h4> <p>You can install NERDTree same as vim-go and coc.nvim. Simply add <code class="language-plaintext highlighter-rouge">Plug 'preservim/nerdtree’</code> to <code class="language-plaintext highlighter-rouge">.vimrc</code> and run <code class="language-plaintext highlighter-rouge">:PlugInstall</code> or run <code class="language-plaintext highlighter-rouge">vim +PlugInstall</code> from command line.</p> <p>Further, you can checkout <a href="https://github.com/preservim/nerdtree/wiki/F.A.Q.">wiki FAQs</a> for setups. My setups for NERDTree is similar to:</p> <div class="language-viml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">map</span> <span class="p">&lt;</span>C<span class="p">-</span>z<span class="p">&gt;</span> <span class="p">:</span>NERDTreeToggle<span class="p">&lt;</span>CR<span class="p">&gt;</span> “ Toggle side <span class="nb">window</span> with `CTRL<span class="p">+</span>z`<span class="p">.</span> <span class="k">let</span> <span class="nv">g:NERDTreeDirArrowExpandable</span> <span class="p">=</span> <span class="s1">'▸'</span> <span class="k">let</span> <span class="nv">g:NERDTreeDirArrowCollapsible</span> <span class="p">=</span> <span class="s1">'▾'</span> <span class="k">let</span> NERDTreeShowHidden<span class="p">=</span><span class="m">1</span> “ Show <span class="nb">hidden</span> <span class="k">files</span> </code></pre></div></div> <p>You can open a directory into vim using <code class="language-plaintext highlighter-rouge">vim .</code> (similar to <code class="language-plaintext highlighter-rouge">code .</code> for VSCode). You can switch between NERDTree and your code by using <code class="language-plaintext highlighter-rouge">CTRL+ww</code> or <code class="language-plaintext highlighter-rouge">CTRL+w+ArrowKey</code>. NERDTree has built-in documentations, you can check them by pressing <code class="language-plaintext highlighter-rouge">?</code> when you are in NERDTree window.</p> <p>This is how my Vim setup look like with NERDTree:</p> <p><img class="fullimg" alt="NERDTree screenshot of Vim" src="https://user-images.githubusercontent.com/20956124/88571617-90503b80-d05b-11ea-9d57-e21714f66636.png" /></p> <p>You can have a look at my <code class="language-plaintext highlighter-rouge">.vimrc</code> and vim scripts on GitHub at <a href="https://github.com/jogendra/dotfiles/tree/master/vim"><strong>jogendra/dotfiles/vim</strong></a>.</p> <h3 id="helpful-links">Helpful Links</h3> <p>Here are some links that will help you get started with Vim:</p> <ul> <li><a href="https://www.openvim.com/">OpenVim - Interactive Vim Tutorials</a></li> <li><a href="https://vim.fandom.com/wiki/Best_Vim_Tips">Best Vim Tips by Fandom</a></li> <li><a href="http://www.viemu.com/a-why-vi-vim.html">Why, oh WHY, do those nutheads use vi?</a></li> <li><a href="https://thoughtbot.com/blog/tags/vim">Collection of Vim articles by thoughtbot</a></li> <li><a href="http://vim.spf13.com/">spf13-vim - The Ultimate Vim Distribution</a></li> <li><a href="https://vim.rtorr.com/">Vim Cheatsheet</a></li> <li><a href="https://joshldavis.com/2014/04/05/vim-tab-madness-buffers-vs-tabs/">Vim Tab Madness. Buffers vs Tabs</a></li> </ul> <p>You can simply run <em>vimtutor</em> in your command line by running <code class="language-plaintext highlighter-rouge">vimtutor</code> command. It will open up Vim tutorials and help you getting started with it.</p> <h3 id="bottom-lines">Bottom Lines</h3> <p>Using vim is fun. Initially you may face some problem using it and you may have to learn how to use it but it is definitely worth learning it. Atleast you can continue learning vim along side using a IDE. Start with the minimum and build a little on it each day. There are a lot of plugins available for everything you need with Vim. Using vim for Golang development is super nice with vim-go plugin. Isn’t it nice to having everything at one place and being more productive?</p> <p>Thanks for reading. Do share with your colleagues/friends. You can reach out to me on <a href="https://twitter.com/jogendrafx">Twitter</a>.</p> Tue, 28 Jul 2020 00:00:00 +0000 https://jogendra.dev/using-vim-for-go-development https://jogendra.dev/using-vim-for-go-development Building Command Line Tools in Go <p>Command line interface (CLI) tools are an essential part of any developer. CLI tools are lightweight, less noisy, fun to use and of course they make you more <strong>productive</strong>. Everyday I try to be more dependent on Terminal (I use <a href="https://www.iterm2.com/">iTerm</a>) and get rid of GUI applications, simply because I am more focused using a tool that provide me everything with just some commands/scripts than some fancy GUI application that come with fancy popups, ads, pictures which add noise to my focus.</p> <p>In this example, we will create a simple (but scalable to any level) bookmark CLI tool in Go using Cobra that can do basic operations like adding a new bookmark, listings, clear etc. I will try to keep it as much detailed as possible.</p> <h3 id="why-go">Why Go?</h3> <p>Go brings a whole set of advantages. Go compiles very quickly into a single binary, works across platforms with a consistent style, and brings a strong development community. Go applications are built into a single self contained binary making installing Go applications trivial. Easy concurrency model makes Go easy to progress with multiple tasks at the same time. Concurrency is an integral part of Go (Selling point of Go :P), supported by goroutines, channels. Go also provides backward compatibility. The list is long, let’s move on to the next part.</p> <p>If you aren’t familiar with Go, you simply go through <a href="https://tour.golang.org/">Go Tour</a> for basic understandings.</p> <p>Go is simply great to build CLI tools. Go might be the fastest and perhaps the most fun way to automate tasks. With Go, you can develop cross-platform command-line tools that are fast and reliable. There are many good libraries out there for building CLI tools and one of such library is <a href="https://github.com/spf13/cobra">Cobra</a>, which we will using in this example project.</p> <h4 id="before-we-start">Before we start</h4> <p>Before we proceed any further, install Go on your machine, if you have not already installed so. For this, you can follow the <a href="https://golang.org/doc/install">installation procedure</a> on the official Golang website. It is good to have recent version of Go on your machine. You can simply execute <code class="language-plaintext highlighter-rouge">go version</code> to check Go version installed on your machine.</p> <h4 id="setting-up-environment-variables">Setting up environment variables</h4> <p>Environment variables, are variables in your system that describe your environment. They are executed and added to your machine environment before you access/use them. During the Go installation, some Go specific environment variables are set to configure behavior of Go tools on your machine. You can check Go environment variables by running <code class="language-plaintext highlighter-rouge">go env</code>.</p> <p>In your <code class="language-plaintext highlighter-rouge">.zshrc</code> or <code class="language-plaintext highlighter-rouge">.bashrc</code> file, append your system’s <strong>$PATH</strong> so that we can invoke <code class="language-plaintext highlighter-rouge">bookmark</code> command from anywhere.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="k">${</span><span class="nv">PATH</span><span class="k">}</span>:<span class="nv">$HOME</span>/go/bin </code></pre></div></div> <blockquote> <p><code class="language-plaintext highlighter-rouge">PATH=${PATH}:path/to/folder</code> is an interesting thing of unix, I will keep it for some other blog post to explain it in details.</p> </blockquote> <p>Restart your terminal once to source them into your machine environment or just <code class="language-plaintext highlighter-rouge">source ~/.zshrc</code>.</p> <h3 id="playing-with-cobra">Playing with <a href="https://github.com/spf13/cobra">Cobra</a></h3> <p><img src="https://cloud.githubusercontent.com/assets/173412/10886352/ad566232-814f-11e5-9cd0-aa101788c117.png" alt="Cobra logo" /></p> <p>Cobra is widely popular Go package and many projects use it for building CLI tools. Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files. Many of the most widely used Go projects are built using Cobra such as <a href="http://kubernetes.io/">Kubernetes</a>, <a href="https://gohugo.io">Hugo</a>, <a href="https://github.com/docker/distribution">Docker (distribution)</a>, <a href="https://github.com/cli/cli">GitHub CLI</a> and <a href="https://github.com/spf13/cobra/blob/master/projects_using_cobra.md">many more big names</a>.</p> <p>Cobra is built on a structure of <em>commands</em>, <em>arguments</em> and <em>flags</em>. Commands represent actions, Args are things and Flags are modifiers for those actions. <em>APPNAME</em> is the name of your tool.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>APPNAME COMMAND ARG <span class="nt">--FLAG</span> </code></pre></div></div> <p>For example:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone URL <span class="nt">--bare</span> </code></pre></div></div> <p>Cobra provides a lot of feature, you can <a href="https://github.com/spf13/cobra#overview">check them out here</a>.</p> <h4 id="installing-cobra">Installing Cobra</h4> <p>You can simply use <code class="language-plaintext highlighter-rouge">go get</code> to install the latest version of Cobra. This command will install the cobra generator executable along with the library and its dependencies.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go get <span class="nt">-u</span> github.com/spf13/cobra/cobra </code></pre></div></div> <p>The cobra binary is now in the <code class="language-plaintext highlighter-rouge">bin/</code> (usually <code class="language-plaintext highlighter-rouge">~/go/bin/cobra</code>) directory, which is itself in your <code class="language-plaintext highlighter-rouge">PATH</code>, so it can be used directly from anywhere. You can run <code class="language-plaintext highlighter-rouge">cobra help</code> or just <code class="language-plaintext highlighter-rouge">cobra</code> to get more familiar with it.</p> <h2 id="lets-get-started">Let’s get started</h2> <p>First step to generate a basic organizational structure that cobra follows. Cobra provides <a href="https://github.com/spf13/cobra/blob/master/cobra/README.md">its own program</a> that will create your application and add any commands you want. It’s the easiest way to incorporate Cobra into your application. Run this in your newly created project directory:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cobra init <span class="nt">--pkg-name</span> bookmark </code></pre></div></div> <p>Here <em><strong>bookmark</strong></em> is our tool name. This will generate basic structure which will look like:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── LICENSE ├── cmd │ └── root.go └── main.go </code></pre></div></div> <p>A package named <strong>cmd</strong> is created which contains all of your commands files. <code class="language-plaintext highlighter-rouge">main.go</code> is the entry point of your tool, it export <em>cmd</em> package and run <code class="language-plaintext highlighter-rouge">Execute()</code> func from <code class="language-plaintext highlighter-rouge">cmd/root.go</code> which execute the root command (base command of your tool, <code class="language-plaintext highlighter-rouge">bookmark</code> in this example). <code class="language-plaintext highlighter-rouge">rootCmd</code> represents the base command when called without any subcommands.</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">var</span> <span class="n">rootCmd</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">cobra</span><span class="o">.</span><span class="n">Command</span><span class="p">{</span> <span class="n">Use</span><span class="o">:</span> <span class="s">"bookmark"</span><span class="p">,</span> <span class="n">Short</span><span class="o">:</span> <span class="s">"A brief description of your application"</span><span class="p">,</span> <span class="n">Long</span><span class="o">:</span> <span class="s">`A longer description that spans multiple lines and likely contains examples and usage of using your application.`</span><span class="p">,</span> <span class="p">}</span> </code></pre></div></div> <p>Here is how <code class="language-plaintext highlighter-rouge">main.go</code> look like:</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span> <span class="k">import</span> <span class="s">"bookmark/cmd"</span> <span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span> <span class="n">cmd</span><span class="o">.</span><span class="n">Execute</span><span class="p">()</span> <span class="p">}</span> </code></pre></div></div> <p>We are importing <code class="language-plaintext highlighter-rouge">bookmark/cmd</code> inside our <code class="language-plaintext highlighter-rouge">main.go</code>. Let’s create <code class="language-plaintext highlighter-rouge">bookmark</code> module before building and using it.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go mod init bookmark </code></pre></div></div> <p>It will create <code class="language-plaintext highlighter-rouge">go.mod</code> file which look like:</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">module</span> <span class="n">bookmark</span> <span class="k">go</span> <span class="m">1.14</span> <span class="n">require</span> <span class="p">(</span> <span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">mitchellh</span><span class="o">/</span><span class="k">go</span><span class="o">-</span><span class="n">homedir</span> <span class="n">v1</span><span class="m">.1.0</span> <span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">spf13</span><span class="o">/</span><span class="n">cobra</span> <span class="n">v1</span><span class="m">.0.0</span> <span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">spf13</span><span class="o">/</span><span class="n">viper</span> <span class="n">v1</span><span class="m">.7.0</span> <span class="p">)</span> </code></pre></div></div> <p>Now, let’s just build this module with:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go build </code></pre></div></div> <p>This build a binary for <code class="language-plaintext highlighter-rouge">bookmark</code> module and generate <code class="language-plaintext highlighter-rouge">go.sum</code> for the same.</p> <p>Run the command below to install the module so that we can run <code class="language-plaintext highlighter-rouge">bookmark</code> command.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>go <span class="nb">install </span>bookmark </code></pre></div></div> <p>Now, if you run <code class="language-plaintext highlighter-rouge">bookmark</code> inside your terminal, it will show the details about the tool, available commands, flags etc. You can notice that it already has <code class="language-plaintext highlighter-rouge">help</code> command, Cobra created that for us.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>A Command Line tool to manage all of your bookmarks at one place. Usage: bookmark <span class="o">[</span><span class="nb">command</span><span class="o">]</span> Available Commands: <span class="nb">help </span>Help about any <span class="nb">command </span>Flags: <span class="nt">--config</span> string config file <span class="o">(</span>default is <span class="nv">$HOME</span>/.bookmark.yaml<span class="o">)</span> <span class="nt">-h</span>, <span class="nt">--help</span> <span class="nb">help </span><span class="k">for </span>bookmark <span class="nt">-t</span>, <span class="nt">--toggle</span> Help message <span class="k">for </span>toggle Use <span class="s2">"bookmark [command] --help"</span> <span class="k">for </span>more information about a command. </code></pre></div></div> <p>Here <em>config file</em> is simple configuration file which will help you eliminate providing a bunch of repeated information in flags over and over. You can <a href="https://github.com/spf13/cobra/blob/master/cobra/README.md#configuring-the-cobra-generator">check out more about it here</a>.</p> <h3 id="creating-our-first-command">Creating our first command</h3> <p>We are done with initial setups. Now, let’s create the first command of our tool. With <a href="https://github.com/spf13/cobra/blob/master/cobra/README.md#cobra-add"><code class="language-plaintext highlighter-rouge">cobra add</code></a>, you can generate new command.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Cobra add &lt;<span class="nb">command</span><span class="o">&gt;</span> </code></pre></div></div> <p>We will create a command called <code class="language-plaintext highlighter-rouge">insert</code> which we will use to add a new bookmark to the bookmarks list.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Cobra add insert </code></pre></div></div> <p>This will create a new go file <code class="language-plaintext highlighter-rouge">insert.go</code> to <em>cmd</em> with the initial format. We will simply store bookmarks into a text file located at <code class="language-plaintext highlighter-rouge">~/.bookmarks/bookmarks.txt</code>.</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">cmd</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"os"</span> <span class="s">"os/exec"</span> <span class="s">"github.com/spf13/cobra"</span> <span class="p">)</span> <span class="c">// insertCmd represents the insert command</span> <span class="k">var</span> <span class="n">insertCmd</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">cobra</span><span class="o">.</span><span class="n">Command</span><span class="p">{</span> <span class="n">Use</span><span class="o">:</span> <span class="s">"insert"</span><span class="p">,</span> <span class="n">Short</span><span class="o">:</span> <span class="s">"Add a new bookmark to bookmarks list"</span><span class="p">,</span> <span class="n">Long</span><span class="o">:</span> <span class="s">`When you run this command with a link or text, it add that link/text to your bookmarks list`</span><span class="p">,</span> <span class="n">Run</span><span class="o">:</span> <span class="k">func</span><span class="p">(</span><span class="n">cmd</span> <span class="o">*</span><span class="n">cobra</span><span class="o">.</span><span class="n">Command</span><span class="p">,</span> <span class="n">args</span> <span class="p">[]</span><span class="kt">string</span><span class="p">)</span> <span class="p">{</span> <span class="n">homeDir</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">UserHomeDir</span><span class="p">()</span> <span class="n">bookmarkFile</span> <span class="o">:=</span> <span class="n">homeDir</span> <span class="o">+</span> <span class="s">"/.bookmarks/bookmarks.txt"</span> <span class="k">if</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">Stat</span><span class="p">(</span><span class="n">bookmarkFile</span><span class="p">);</span> <span class="n">os</span><span class="o">.</span><span class="n">IsNotExist</span><span class="p">(</span><span class="n">err</span><span class="p">)</span> <span class="p">{</span> <span class="n">os</span><span class="o">.</span><span class="n">Mkdir</span><span class="p">(</span><span class="n">homeDir</span> <span class="o">+</span> <span class="s">"/.bookmarks"</span><span class="p">,</span> <span class="m">0700</span><span class="p">)</span> <span class="n">os</span><span class="o">.</span><span class="n">Create</span><span class="p">(</span><span class="n">homeDir</span> <span class="o">+</span> <span class="s">"/.bookmarks/bookmarks.txt"</span><span class="p">)</span> <span class="p">}</span> <span class="n">f</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">OpenFile</span><span class="p">(</span><span class="n">bookmarkFile</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">O_APPEND</span><span class="o">|</span><span class="n">os</span><span class="o">.</span><span class="n">O_WRONLY</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">ModeAppend</span><span class="p">)</span> <span class="n">f</span><span class="o">.</span><span class="n">WriteString</span><span class="p">(</span><span class="n">args</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span> <span class="n">f</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span> <span class="p">},</span> <span class="p">}</span> <span class="k">func</span> <span class="n">init</span><span class="p">()</span> <span class="p">{</span> <span class="n">rootCmd</span><span class="o">.</span><span class="n">AddCommand</span><span class="p">(</span><span class="n">insertCmd</span><span class="p">)</span> <span class="p">}</span> </code></pre></div></div> <p>Every command will have a kind of similar structure to this. The <code class="language-plaintext highlighter-rouge">Run</code> inside the <code class="language-plaintext highlighter-rouge">insertCmd</code> is called when you run <code class="language-plaintext highlighter-rouge">bookmark insert</code> so we have implemented very simple functionality inside it. It simply checks if <code class="language-plaintext highlighter-rouge">~/.bookmarks/bookmarks.txt</code> exist, if not, create one otherwise add this new bookmark text/link (which we will provide in argument) to the database which is simply a text file.</p> <p>Now, you can use <code class="language-plaintext highlighter-rouge">insert</code> command like this:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bookmark insert <span class="s2">"this is my first bookmark, i love this tool"</span> </code></pre></div></div> <p>Woah! You just created your first command successfully. Easy?</p> <h3 id="adding-more-commands">Adding more commands</h3> <p>We will add couple of more commands to our tool. The procedure is same as what we followed in creating our first command.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cobra add list cobra add clearall </code></pre></div></div> <p>We will use our newly created <code class="language-plaintext highlighter-rouge">list</code> command to display all the bookmarks in our bookmark database and <code class="language-plaintext highlighter-rouge">clearall</code> to clear all the bookmarks from databse. You can check out <a href="https://github.com/jogendra/bookmark-cli/blob/master/cmd/list.go"><code class="language-plaintext highlighter-rouge">cmd/list.go</code></a> and <a href="https://github.com/jogendra/bookmark-cli/blob/master/cmd/clearall.go"><code class="language-plaintext highlighter-rouge">cmd/clearall.go</code></a> files.</p> <p>Now, if you run <code class="language-plaintext highlighter-rouge">bookmark help</code> in terminal, it will show newly added commands too. Make sure you run <code class="language-plaintext highlighter-rouge">go install bookmark</code> to see new changes.</p> <h3 id="implementing-subcommands">Implementing Subcommands</h3> <p>In this part, we will add a subcommand <code class="language-plaintext highlighter-rouge">last</code> to our <code class="language-plaintext highlighter-rouge">bookmark list</code> command. This subcommand will display the last most added bookmark. The cobra command for creating subcommand is like below:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cobra add &lt;subcommand&gt; <span class="nt">-p</span> &lt;parent <span class="nb">command</span><span class="o">&gt;</span> </code></pre></div></div> <p>Let’s add <code class="language-plaintext highlighter-rouge">last</code> subcommand to <code class="language-plaintext highlighter-rouge">list</code> with:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cobra add last <span class="nt">-p</span> listCmd </code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">listCmd</code> is internal representation of <code class="language-plaintext highlighter-rouge">list</code> command.</p> <p>Our implementation for <code class="language-plaintext highlighter-rouge">last</code> subcommand look like this in <code class="language-plaintext highlighter-rouge">cmd/last.go</code> file:</p> <div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">cmd</span> <span class="k">import</span> <span class="p">(</span> <span class="s">"fmt"</span> <span class="s">"os"</span> <span class="s">"bufio"</span> <span class="s">"github.com/spf13/cobra"</span> <span class="p">)</span> <span class="c">// lastCmd represents the last command</span> <span class="k">var</span> <span class="n">lastCmd</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">cobra</span><span class="o">.</span><span class="n">Command</span><span class="p">{</span> <span class="n">Use</span><span class="o">:</span> <span class="s">"last"</span><span class="p">,</span> <span class="n">Short</span><span class="o">:</span> <span class="s">"Show the last most bookmark added"</span><span class="p">,</span> <span class="n">Long</span><span class="o">:</span> <span class="s">`Show the last most bookmark added from the bookmarks list`</span><span class="p">,</span> <span class="n">Run</span><span class="o">:</span> <span class="k">func</span><span class="p">(</span><span class="n">cmd</span> <span class="o">*</span><span class="n">cobra</span><span class="o">.</span><span class="n">Command</span><span class="p">,</span> <span class="n">args</span> <span class="p">[]</span><span class="kt">string</span><span class="p">)</span> <span class="p">{</span> <span class="n">homeDir</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">UserHomeDir</span><span class="p">()</span> <span class="k">if</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">Stat</span><span class="p">(</span><span class="n">homeDir</span> <span class="o">+</span> <span class="s">"/.bookmarks/bookmarks.txt"</span><span class="p">);</span> <span class="n">os</span><span class="o">.</span><span class="n">IsNotExist</span><span class="p">(</span><span class="n">err</span><span class="p">)</span> <span class="p">{</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"Your bookmarks list is empty"</span><span class="p">)</span> <span class="k">return</span> <span class="p">}</span> <span class="n">f</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">Open</span><span class="p">(</span><span class="n">homeDir</span> <span class="o">+</span> <span class="s">"/.bookmarks/bookmarks.txt"</span><span class="p">)</span> <span class="k">defer</span> <span class="n">f</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span> <span class="n">scanner</span> <span class="o">:=</span> <span class="n">bufio</span><span class="o">.</span><span class="n">NewScanner</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="k">var</span> <span class="n">lastLine</span> <span class="kt">string</span> <span class="k">for</span> <span class="n">scanner</span><span class="o">.</span><span class="n">Scan</span><span class="p">()</span> <span class="p">{</span> <span class="n">lastLine</span> <span class="o">=</span> <span class="n">scanner</span><span class="o">.</span><span class="n">Text</span><span class="p">()</span> <span class="p">}</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"&gt;"</span><span class="p">,</span> <span class="n">lastLine</span><span class="p">)</span> <span class="p">},</span> <span class="p">}</span> <span class="k">func</span> <span class="n">init</span><span class="p">()</span> <span class="p">{</span> <span class="n">listCmd</span><span class="o">.</span><span class="n">AddCommand</span><span class="p">(</span><span class="n">lastCmd</span><span class="p">)</span> <span class="p">}</span> </code></pre></div></div> <p>Now, if we run <code class="language-plaintext highlighter-rouge">bookmark list last</code>, it will display the last bookmark added. Don’t forget to rebuild binary with <code class="language-plaintext highlighter-rouge">go install bookmark</code> before running command. You can check newly added subcommand on <code class="language-plaintext highlighter-rouge">list</code> by running <code class="language-plaintext highlighter-rouge">bookmark list --help</code></p> <p><img class="fullimg" alt="bookmark-cli-subcommand" src="https://user-images.githubusercontent.com/20956124/86530983-1bb23300-bedb-11ea-9906-b9ad5ec5043e.png" /></p> <p>The <code class="language-plaintext highlighter-rouge">last</code> subcommand is considered a command line argument to the <code class="language-plaintext highlighter-rouge">list</code> command. Similarly, you can add subcommands to other commands and to subcommands itself.</p> <p>Next, you can implement/add flags to your commands. It is very easy to working with flags in Cobra. You can follow their <a href="https://github.com/spf13/cobra#working-with-flags">Readme/docs on flags</a>. What we done is very basic, you can do lot with Cobra.</p> <h3 id="source-code-sturcture">Source Code Sturcture</h3> <p>After implementing commands and subcommands, project structure looks like below:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">.</span> ├── LICENSE ├── README.md ├── cmd │   ├── clearall.go │   ├── insert.go │   ├── last.go │   ├── list.go │   └── root.go ├── go.mod ├── go.sum └── main.go 1 directory, 10 files </code></pre></div></div> <p>Run <code class="language-plaintext highlighter-rouge">go install bookmark</code> to rebuild binary.</p> <p>Now, if you run <code class="language-plaintext highlighter-rouge">bookmark help</code> in terminal, it will look like this:</p> <p><img class="fullimg" alt="bookmark-cli screenshot" src="https://user-images.githubusercontent.com/20956124/86528214-7e97d000-bec3-11ea-8d21-a2d3f7eff485.png" /></p> <h4 id="full-source-code-jogendrabookmark-cli">Full source code: <a href="https://github.com/jogendra/bookmark-cli">jogendra/bookmark-cli</a></h4> <h3 id="wrapping-up">Wrapping up</h3> <p>So now you have successfully made a user-friendly CLI tool which can perform basic operations. You can check out the full source code at <a href="https://github.com/jogendra/bookmark-cli">jogendra/bookmark-cli</a>. You have seen how easy and scalable it is to make a CLI tool in Go using Cobra. Go is simply great language choice to build CLI tools and Cobra is undoubtly first choice. Main aim is to create basic understanding. I will keep updating it with more basic operations if it requires. Thanks for reading. Looking forword to see what you are building.</p> Sun, 05 Jul 2020 00:00:00 +0000 https://jogendra.dev/building-command-line-tools-in-go https://jogendra.dev/building-command-line-tools-in-go I do Dotfiles! <p>One of the main advantages/beauty of Unix-like systems is that configurations of everything are very customizable. Dotfiles are simply amazing. They are tiny-little configurations files but they customize/decide a lot of your system. Well-organized and understandable dotfiles basically allow you to automatize complex tasks that you repeat everyday. With dotfiles, your machine is more organized, advanced, and customized in the way you like your system to be. Also with dotfiles, you can mess around with new packages and settings quite easily, without ever having to worry about really breaking something important. If you would break something, your dotfiles are always right there to back you up.</p> <blockquote> <p><strong>TL;DR</strong>: Invest time learning to configure your machine and automate processes, you’ll get that time back tenfold.</p> </blockquote> <blockquote> <p>Your experience, knowledge and talent are key to success, but some kick-ass dotfiles speed you up.</p> </blockquote> <h4 id="rocket-sparkles-my-dotfiles-can-be-found-at-jogendradotfiles">:rocket: :sparkles: <em>My dotfiles can be found at <a href="https://github.com/jogendra/dotfiles"><strong>jogendra/dotfiles</strong></a></em></h4> <h3 id="what-are-dotfiles">What are dotfiles?</h3> <p>Dotfiles are files and folders on Unix-like systems starting with <code class="language-plaintext highlighter-rouge">.</code> (dot) that control the configuration of applications and shells on your system. Dotfiles are shell scripts that are executed to change the environment of your machine. The <strong>“dotfiles”</strong> name is derived from the configuration files in Unix-like systems that start with a dot (e.g. <em>.zshrc</em> and <em>.gitconfig</em>). For normal users, this indicates these are not regular documents, and by default are hidden in directory listings. You can display dotfiles inside any directory by running <code class="language-plaintext highlighter-rouge">ls -a</code> command. <a href="https://dotfiles.github.io/">GitHub does dotfiles</a> is definitely great place to explore and know more about dotfiles.</p> <p>Why dotfiles start with <code class="language-plaintext highlighter-rouge">.</code> (dot) is an interesting example of <strong>a bug that has become a feature</strong>. Checkout interesting reads on <a href="https://www.reddit.com/r/linux/comments/at05xh/why_do_hidden_files_in_unix_begin_with_a_dot/egyj6lr/">Why do hidden files in Unix begin with a dot?</a>.</p> <p><img class="fullimg" alt="dotfiles" src="https://user-images.githubusercontent.com/20956124/84434654-14866500-ac4e-11ea-8de3-413870e37893.png" /></p> <h3 id="github-codespaces-and-dotfiles">GitHub Codespaces and Dotfiles</h3> <p>Recently GitHub announced Codespaces. According to GitHub, Codespaces uses your dotfiles repository on GitHub to personalize every new codespace that you create. Anyone can create a dotfiles repository to personalize Codespaces for their user account. If your user account on GitHub owns a public repository named dotfiles, GitHub automatically uses this repository to personalize your codespace environment. Read more about this <a href="https://help.github.com/en/github/developing-online-with-codespaces/personalizing-codespaces-for-your-account">here</a>.</p> <h3 id="sharing-your-dotfiles">Sharing your dotfiles</h3> <p>The main reason behind creating dotfiles is customizing your machine the way you like. Imagine the situation when your machine got broken or some reason you have to change your machine or you have changed your workplace so the machine, in that case, if you haven’t put your dotfiles on some remote machine, you will have to create the dotfile again which was was your long time iterations. With dotfiles, you can have a new machine ready in hours, providing the exact same user experience as the previous one. You can publicly put them on GitHub/Dropbox/etc or keep them private. It is worth keeping your dotfiles under version control for several reasons. Keeping a history of your changes allows you to safely experiment with new configurations, revert bad changes, and review the details of past changes. It is recommended to use different branch for different systems eg. you can put macOS focused dotfiles in one branch and linux system focused dotfiles on another branch so that it become easier for you when you shift between systems. I have made <a href="https://github.com/jogendra/dotfiles">my own dotfiles</a> open sourced, and parts of my configuration are inspired by other people’s dotfiles. Afterall, dotfiles are meant for sharing.</p> <h3 id="do-not-blindly-copy-dotfiles">Do not blindly copy dotfiles</h3> <p>Mostly, dotfiles are very specific to the individual developer. What works for someone else isn’t necessarily optimal for you. Rather than using someone else’s dotfiles, it is recommended to create your own dotfiles. But it is always great to look into other’s dotfiles to get better ideas, also you can use a bit and pieces of their profiles if you think that will be helpful to you. Going through other’s dotfiles is a great way to improve your own dotfiles. Before creating mine, I went through a lot of other’s dotfiles on GitHub. I took this idea of organizing (structure) dotfiles from <a href="https://github.com/holman/dotfiles">Holman dotfiles</a>. You can get ideas from different dotfiles and cherry pick some of their part into yours, so for the same reason you can make your dotfiles public on GitHub/GitLab etc. and let the other developers pick and choose from it. I do not agree with <a href="http://zachholman.com/2010/08/dotfiles-are-meant-to-be-forked/">“dotfiles are meant to be forked”</a>.</p> <h3 id="securing-the-dotfiles">Securing the dotfiles</h3> <p>Dotfiles often contain some private data like plain text passwords and some pieces of information you don’t wanna share publicly. Anything that is a security risk, like files in your <code class="language-plaintext highlighter-rouge">.ssh/</code> folder, is not a good choice to share using this method. Be sure to double-check your configuration files before publishing them online and triple-check that no API tokens are in your files. You can use <a href="https://git-scm.com/docs/gitattributes">gitattributes</a> for git related sensitive information. Version controlled folder’s files that contain sensitive information and should not be published are kept secret using package-specific <code class="language-plaintext highlighter-rouge">.gitignore</code> files.</p> <p><code class="language-plaintext highlighter-rouge">git config credential.helper store</code> is not a very secure way to store your git server passwords. According to <a href="https://git-scm.com/docs/git-credential-store">git credential store documentations</a>, <em>store</em> helper will store your passwords unencrypted on disk, protected only by filesystem permissions. The <code class="language-plaintext highlighter-rouge">~/.git-credentials</code> file will have its filesystem permissions set to prevent other users on the system from reading it, but will not be encrypted or otherwise protected. So it stores your password as is. So if you are running <em>macOS</em> on your machine, Git allows to use your <em>keychain</em>, which is way more secure. To set <code class="language-plaintext highlighter-rouge">osxkeychain</code> as your git credential helper, run:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config <span class="nt">--global</span> credential.helper osxkeychain </code></pre></div></div> <p>If you are runnning <em>Linux</em> on your machine, you can use <a href="https://git-scm.com/docs/git-credential-cache"><code class="language-plaintext highlighter-rouge">git config credential.helper cache</code></a>, which stores passwords in your memory. This way stored credentials never touch the disk, and are forgotten after a configurable timeout. To set <code class="language-plaintext highlighter-rouge">cache</code> as your git credential helper, run:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config <span class="nt">--global</span> credential.helper cache </code></pre></div></div> <p>Mistakenly, if you commit sensitive data, such as a password or SSH key into dotfiles Git repository, you can remove it from the history. To entirely remove unwanted files from a repository’s history you can use either the <a href="https://git-scm.com/docs/git-filter-branch"><code class="language-plaintext highlighter-rouge">git filter-branch</code></a> command or the <a href="https://rtyley.github.io/bfg-repo-cleaner/">BFG Repo-Cleaner</a> (an alternative to <code class="language-plaintext highlighter-rouge">git filter-branch</code>) open source tool. You can check out GitHub’s guide on <a href="https://help.github.com/en/github/authenticating-to-github/removing-sensitive-data-from-a-repository">Removing sensitive data from a repository</a>.</p> <p>I would recommend you to use <a href="https://www.passwordstore.org/">Passwordstore</a> (pass), the standard Unix password manager. It provides a secure way to encrypt, share, manage, and use private data or passwords. <strong>pass</strong> makes managing these individual password files extremely easy. All passwords live in <code class="language-plaintext highlighter-rouge">~/.password-store</code>, and <code class="language-plaintext highlighter-rouge">pass</code> provides some nice commands for adding, editing, generating, and retrieving passwords. It is a very short and simple shell script. It’s capable of temporarily putting passwords on your clipboard and tracking password changes using <code class="language-plaintext highlighter-rouge">git</code>. You can <a href="https://git.zx2c4.com/password-store/about/">read more about pass here</a>.</p> <p>Do checkout <a href="https://www.outcoldman.com/en/archive/2015/09/17/keep-sensitive-data-encrypted-in-dotfiles/">Keep sensitive data encrypted in dotfiles</a>. Also, check out <a href="https://abdullah.today/encrypted-dotfiles/">how to secure your dotfiles</a>.</p> <h3 id="using-dotfiles-through-ssh">Using dotfiles through SSH</h3> <p>This section is for those who login often on remote servers using SSH. When you enter into the remote server, your ZSH, plugins, all of your aliases, completions, color schemes, <em><strong>NOTHING</strong> is there</em>. Everything set to their default values because your remote shell does not have access back to your local configurations. Terrible?. The solution to this, what I would recommend is to write a script that contains everything you want to put on your remote machine and install there whenever you log in. Keep this script separate from your dotfile installation script because you may not want/need to install everything that is inside your dotfiles repository. Also, because you may not want to slow down your remote server login process. You can also use this SSH wrapper tool called <a href="https://github.com/fsquillace/kyrat"><strong>kyrat</strong></a> that allows you to source local dotfiles on an SSH session to a remote host. It works either from/to a <em>Linux</em> or <em>OSX</em> machine.</p> <hr /> <h3 id="building-my-own-dotfiles">Building my own dotfiles</h3> <p>I came across a dotfiles repository of someone on GitHub sometime ago, I didn’t know about them before. I have always been a CLI (Command Line Interface) fanatic. I keep changing CLI configs, themes, etc time to time. I have been using <em>iTerm2</em> with <em>ZSH</em> and <em>oh-my-zsh</em> from long ago. Above everything, I love scripting in general and automating stuff. For the same reason, I love shell scripting. I went through a lot of repositories from various people, but every single one of them seemed to be totally different because again dotfiles are very specific to the individual developer.</p> <p>My dotfiles contain freshly brewed configuration files for various CLI applications based on macOS. With plenty of useful shell/Git aliases, shell scripts, AppleScripts, and command defaults, etc, it features beautifully customized ZSH and iTerm2 environments and a lot more stuff.</p> <h4 id="jogendradotfiles"><a href="https://github.com/jogendra/dotfiles">jogendra/dotfiles</a></h4> <p>When I was looking for a way to organize my dotfiles, I found <a href="https://github.com/holman/dotfiles">Zach Holman’s dotfiles</a> way of organizing the dotfiles is the best which is called <strong>Topical organization</strong> which is organizing the different parts of dotfiles in directories, each entitled to a specific subject. By topical organization, all AppleScripts go inside the <code class="language-plaintext highlighter-rouge">AppleScript/</code> directory, git settings are in a <code class="language-plaintext highlighter-rouge">git/</code> directory, and so on. Everything’s built around topic areas. If you’re adding a new area to your forked dotfiles — say, <em>“vscode”</em> — you can simply add a vscode directory and put files in there. Anything with an extension of <code class="language-plaintext highlighter-rouge">.zsh</code> will get automatically included in your shell. Anything with an extension of <em><code class="language-plaintext highlighter-rouge">.symlink</code></em> will get symlinked without extension into <code class="language-plaintext highlighter-rouge">$HOME</code> when you run <code class="language-plaintext highlighter-rouge">installers/bootstrap</code>.</p> <p><img class="sideimg" align="right" alt="" src="https://user-images.githubusercontent.com/20956124/84314457-b55d1d80-ab85-11ea-9bed-6009dcb8be18.png" /></p> <p>There are a few special files in the hierarchy:</p> <ul> <li><strong>bin/</strong>: Anything in <code class="language-plaintext highlighter-rouge">bin/</code> will get added to your <code class="language-plaintext highlighter-rouge">$PATH</code> and be made available everywhere.</li> <li><strong>topic/*.zsh</strong>: Any files ending in <code class="language-plaintext highlighter-rouge">.zsh</code> get loaded into your environment.</li> <li><strong>topic/path.zsh</strong>: Any file named <code class="language-plaintext highlighter-rouge">path.zsh</code> is loaded first and is expected to set up <code class="language-plaintext highlighter-rouge">$PATH</code> or similar.</li> <li><strong>topic/completion.zsh</strong>: Any file named <em><code class="language-plaintext highlighter-rouge">completion.zsh</code></em> is loaded last and is expected to set up autocomplete.</li> <li><strong>topic/install.sh</strong>: Any file named <code class="language-plaintext highlighter-rouge">install.sh</code> is executed when you run <code class="language-plaintext highlighter-rouge">installers/install</code>. To avoid being loaded automatically, its extension is <code class="language-plaintext highlighter-rouge">.sh</code>, not <code class="language-plaintext highlighter-rouge">.zsh</code>.</li> <li><strong>topic/*.symlink</strong>: Any file ending in <code class="language-plaintext highlighter-rouge">*.symlink</code> gets symlinked into your <code class="language-plaintext highlighter-rouge">$HOME</code>. This is so you can keep all of those versions in your dotfiles but still keep those autoloaded files in your home directory. These get symlinked in when you run <code class="language-plaintext highlighter-rouge">installers/bootstrap</code>.</li> </ul> <hr /> <h3 id="the-components">The Components</h3> <h4 id="applescripts">AppleScripts</h4> <p>AppleScript is a scripting language developed by Apple to help people automate their work processes on the MacOS. AppleScript is an extremely simple, almost English-like language, but automation it does is pure gold. I sometimes write AppleScripts to automate stuff on my mac where it involves a lot of clicks or similar and I use them using shell aliases. Here is a simple AppleScript that I wrote to open <em>iTerm2</em> tabs with right directory for all work-related projects.</p> <div class="language-applescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">property</span><span class="w"> </span><span class="nv">examplePath1</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"~/Projects/pathto/directory1"</span><span class="w"> </span><span class="k">property</span><span class="w"> </span><span class="nv">examplePath2</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"~/Projects/pathto/directory2"</span><span class="w"> </span><span class="k">property</span><span class="w"> </span><span class="nv">examplePath3</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"~/Projects/pathto/directory3"</span><span class="w"> </span><span class="k">property</span><span class="w"> </span><span class="nv">pathList</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nv">examplePath1</span><span class="p">,</span><span class="w"> </span><span class="nv">examplePath2</span><span class="p">,</span><span class="w"> </span><span class="nv">examplePath3</span><span class="p">}</span><span class="w"> </span><span class="k">on</span> <span class="nv">run_iterm</span><span class="p">()</span><span class="w"> </span><span class="k">tell</span><span class="w"> </span><span class="nb">application</span><span class="w"> </span><span class="s2">"iTerm"</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="nv">newWindow</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="nv">current</span><span class="w"> </span><span class="na">window</span><span class="w"> </span><span class="c1">-- if you want to open repositories in new window</span><span class="w"> </span><span class="c1">-- uncomment the line below and comment linne above</span><span class="w"> </span><span class="c1">-- set newWindow to (create window with default profile)</span><span class="w"> </span><span class="k">tell</span><span class="w"> </span><span class="nv">current</span><span class="w"> </span><span class="nv">session</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nv">newWindow</span><span class="w"> </span><span class="k">repeat</span><span class="w"> </span><span class="nv">with</span><span class="w"> </span><span class="na">path</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="nv">pathList</span><span class="w"> </span><span class="k">tell</span><span class="w"> </span><span class="nv">newWindow</span><span class="w"> </span><span class="nv">create</span><span class="w"> </span><span class="nb">tab</span><span class="w"> </span><span class="nv">with</span><span class="w"> </span><span class="nv">default</span><span class="w"> </span><span class="nv">profile</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">tell</span><span class="w"> </span><span class="nb">write</span><span class="w"> </span><span class="nb">text</span><span class="w"> </span><span class="s2">"cd "</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="na">path</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">repeat</span><span class="w"> </span><span class="nb">delay</span><span class="w"> </span><span class="mf">0.6</span><span class="w"> </span><span class="nb">select</span><span class="w"> </span><span class="nb">first</span><span class="w"> </span><span class="nb">tab</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="nv">newWindow</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">tell</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">tell</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="nv">run_iterm</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nb">application</span><span class="w"> </span><span class="s2">"iTerm"</span><span class="w"> </span><span class="ow">is</span><span class="w"> </span><span class="nv">running</span><span class="w"> </span><span class="k">then</span><span class="w"> </span><span class="nv">run_iterm</span><span class="p">()</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="nb">activate</span><span class="w"> </span><span class="nb">application</span><span class="w"> </span><span class="s2">"iTerm"</span><span class="w"> </span><span class="nv">run_iterm</span><span class="p">()</span><span class="w"> </span><span class="k">end</span><span class="w"> </span><span class="k">if</span><span class="w"> </span></code></pre></div></div> <p>I simply use it with a shell alias:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">alias </span><span class="nv">workinit</span><span class="o">=</span><span class="s2">"osascript ~/dotfiles/AppleScripts/work_init.scpt"</span> </code></pre></div></div> <p>I discovered AppleScript very recently. I will be writing a lot more of them and automate the stuff.</p> <h4 id="homebrew-and-homebrew-cask">Homebrew and Homebrew Cask</h4> <p>Homebrew is very essential for machines running macOS. It helps you install packages and tools in an easy way through the CLI. Homebrew can also help install your apps. You won’t need to manually download and install packages and tools anymore. <a href="https://github.com/Homebrew/homebrew-cask">Homebrew Cask</a> has the power to install GUI applications in macOS from the command line.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew cask <span class="nb">install </span>iterm2 brew cask <span class="nb">install </span>charles brew cask <span class="nb">install </span>firefox brew cask <span class="nb">install </span>google-chrome brew cask <span class="nb">install </span>spotify </code></pre></div></div> <p>Homebrew does not support downloading applications from the App Store. To install App Store apps using CLI, you can use a tool called <a href="https://github.com/mas-cli/mas">mas</a>.</p> <p>My <a href="https://github.com/jogendra/dotfiles/tree/master/brew"><code class="language-plaintext highlighter-rouge">brew/</code></a> contains an <a href="https://raw.githubusercontent.com/jogendra/dotfiles/master/brew/install.sh">install script</a> that firstly checks whether homebrew is already installed or not. If not, download it otherwise get to next part. Next, I have listed some essential brews and casks that I use in my day to day dev life, which will be installed. And at last, it runs update and up-gradation following by cleanup.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span> <span class="c"># Abort on error</span> <span class="nb">set</span> <span class="nt">-e</span> <span class="nb">echo</span> <span class="s2">"Checking if Homebrew is already installed..."</span><span class="p">;</span> <span class="c"># Checks if Homebrew is installed</span> <span class="k">if </span><span class="nb">test</span> <span class="o">!</span> <span class="si">$(</span>which brew<span class="si">)</span><span class="p">;</span> <span class="k">then </span><span class="nb">echo</span> <span class="s2">"Installing Homebrew..."</span><span class="p">;</span> <span class="nb">yes</span> | /bin/bash <span class="nt">-c</span> <span class="s2">"</span><span class="si">$(</span>curl <span class="nt">-fsSL</span> https://raw.githubusercontent.com/Homebrew/install/master/install.sh<span class="si">)</span><span class="s2">"</span> <span class="k">else </span><span class="nb">echo</span> <span class="s2">"Homebrew is already installed..."</span><span class="p">;</span> <span class="k">fi</span> <span class="c"># Install the essential brews</span> brew <span class="nb">install </span>carthage brew <span class="nb">install </span>cocoapods brew <span class="nb">install </span>gettext brew <span class="nb">install </span>gh brew <span class="nb">install </span>git brew <span class="nb">install </span>node brew <span class="nb">install </span>lazygit brew <span class="nb">install </span>lsd brew <span class="nb">install </span>tree brew <span class="nb">install </span>yarn brew <span class="nb">install </span>zsh brew <span class="nb">install </span>zsh-completions brew <span class="nb">install </span>neovim brew <span class="nb">install </span>pass brew <span class="nb">install </span>vim <span class="c"># Install essential casks</span> brew cask <span class="nb">install </span>iterm2 brew cask <span class="nb">install </span>charles brew cask <span class="nb">install </span>firefox <span class="c"># Update and Upgrade</span> <span class="nb">echo</span> <span class="s2">"Updating and upgrading Homebrew..."</span><span class="p">;</span> <span class="nb">echo</span><span class="p">;</span> <span class="nb">yes</span> | brew update <span class="nb">yes</span> | brew upgrade <span class="c"># Remove outdated versions from the cellar</span> brew cleanup </code></pre></div></div> <h4 id="git-configurations">Git Configurations</h4> <p>My <a href="https://github.com/jogendra/dotfiles/tree/master/git"><code class="language-plaintext highlighter-rouge">git/</code></a> contains <a href="https://github.com/jogendra/dotfiles/blob/master/git/gitconfig"><code class="language-plaintext highlighter-rouge">.gitconfig</code></a> and some bash scripts related to git. I hate the idea of creating short aliases for every single basic git commands like <code class="language-plaintext highlighter-rouge">ga</code> for <code class="language-plaintext highlighter-rouge">git add .</code>, <code class="language-plaintext highlighter-rouge">gc</code> for <code class="language-plaintext highlighter-rouge">git commit</code> etc. They create confusion instead of saving time. I use git aliases for little longer git commands or git processes that involve more than two series of commands. I simply put them in a bash file and use them as aliases. You can simply use them according to your convenience. I have <strong>hardlinked</strong> my dotfiles <code class="language-plaintext highlighter-rouge">.gitconfig</code> with my system’s global <code class="language-plaintext highlighter-rouge">.gitconfig</code>. They are <strong>hardlinked</strong> so that I can use the same in my other systems.</p> <p>A <strong>symbolic (symlink)</strong> or <strong>soft link</strong> is an actual link to the original file, whereas a <strong>hard link</strong> is a mirror copy of the original file. If you delete the original file, the soft link has no value, because it points to a non-existent file, symlinked file will be useless. But in the case of hard link, it is entirely opposite. Even if you delete the original file, the hard link will still has the data of the original file. Because hard link acts as a mirror copy of the original file. In other words, indode number for soft linked files are different (point to different inode) but on the other hand inode number for hard linked files are same (point to same inode). In unix-like systems, <strong>Inode</strong> is a data-structure that represents a file or a directory. An <strong>Inode number</strong> is a unique number given to an inode.</p> <p>You can simply create a hard link by running:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ln</span> ~/.gitconfig gitconfig </code></pre></div></div> <p>OR if you want to create a soft link instead of a hard link, just use <code class="language-plaintext highlighter-rouge">-s</code> option.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ln</span> <span class="nt">-s</span> ~/.gitconfig gitconfig </code></pre></div></div> <p>You can do the same for other git configuration files like <code class="language-plaintext highlighter-rouge">.gitignore</code>, <code class="language-plaintext highlighter-rouge">.gitattributes</code> etc.</p> <p>Please be careful before putting any sensitive data into git configs files that you are going to put public on GitHub. You can refer to <em><strong>Securing the dotfiles</strong></em> section of this blog.</p> <p>My <code class="language-plaintext highlighter-rouge">.gitconfig</code> look like this:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>user] name <span class="o">=</span> jogendra email <span class="o">=</span> [email protected] <span class="o">[</span>mergetool] keepBackup <span class="o">=</span> <span class="nb">false</span> <span class="o">[</span><span class="nb">alias</span><span class="o">]</span> parent <span class="o">=</span> rev-parse <span class="nt">--abbrev-ref</span> <span class="nt">--symbolic-full-name</span> @<span class="o">{</span>u<span class="o">}</span> last <span class="o">=</span> log <span class="nt">-1</span> HEAD unstage <span class="o">=</span> reset HEAD <span class="nt">--</span> <span class="nb">pr</span> <span class="o">=</span> <span class="o">!</span>sh ~/dotfiles/git/pull_request.sh remotes <span class="o">=</span> remote <span class="nt">-v</span> syncu <span class="o">=</span> <span class="o">!</span>sh ~/dotfiles/git/sync_with_upstream.sh diffall <span class="o">=</span> <span class="o">!</span>sh ~/dotfiles/git/git_unpushed.sh plog <span class="o">=</span> <span class="o">!</span>sh ~/dotfiles/git/plog.sh lazy <span class="o">=</span> <span class="o">!</span>lazygit contributors <span class="o">=</span> shortlog <span class="nt">--summary</span> <span class="nt">--numbered</span> <span class="o">[</span>color] diff <span class="o">=</span> auto status <span class="o">=</span> auto branch <span class="o">=</span> auto ui <span class="o">=</span> <span class="nb">true</span> <span class="o">[</span>commit] gpgsign <span class="o">=</span> <span class="nb">false</span> <span class="o">[</span>core] editor <span class="o">=</span> vim excludesfile <span class="o">=</span> ~/.gitignore <span class="o">[</span>credential] helper <span class="o">=</span> osxkeychain </code></pre></div></div> <h4 id="zsh-and-oh-my-zsh">ZSH and OH-MY-ZSH</h4> <p>Command-line is arguably one of the most important tools for any power user, so configuring it in the right way is really important. I prefer Z Shell over any other shell. I am using Z Shell from last almost 3 years now and did not found a reason to use another shell. I used to use Bash before that. Zsh is a powerful shell that features some great improvements over Bash like autocompletion, shared command history, themeable prompts, and a lot more. From the customizations perspective, no one can replace ZSH. There are many ZSH configuration frameworks available, <a href="https://ohmyz.sh/">oh-my-zsh</a> is the most used/popular one. <a href="https://support.apple.com/en-ca/HT208050">ZSH is also been made default shell for macOS from Catalina</a>. For writing scripts, I prefer Bash over Zsh. You can get more insight into <a href="https://apple.stackexchange.com/a/361957/266414">practical differences between Bash and Zsh</a>. Some people also prefer using <a href="https://fishshell.com/">Fish</a> shell over Bash and Zsh.</p> <p><a href="https://ohmyz.sh/"><strong>oh-my-zsh</strong></a>: Oh-my-zsh is simply amazing. Without it, it is difficult to customize/configure ZSH. <em>Oh-My-Zsh</em> is wildly adopted, has a strong community, and a great range of features. I use <em>Oh-My-Zsh</em> with <a href="https://github.com/romkatv/powerlevel10k"><strong>Powerlevel10k</strong></a> theme. My favorite thing about <em>Powerlevel10k</em> is its <a href="https://github.com/Powerlevel9k/powerlevel9k">Powerlevel9k</a> compatibility. I use a lot of <em>Powerlevel9k</em> customizations on <em>Powerlevel10k</em> and <em>source</em> them in <code class="language-plaintext highlighter-rouge">zshrc</code>. The <a href="https://github.com/subnixr/minimal">minimal theme</a> is also nice to use.</p> <p>My <a href="https://github.com/jogendra/dotfiles/tree/master/zsh"><code class="language-plaintext highlighter-rouge">zsh/</code></a> contains <code class="language-plaintext highlighter-rouge">zshrc</code> which I kept very clean by putting similar thighs into different files eg. all aliases into <code class="language-plaintext highlighter-rouge">aliases</code> file, configurations in <code class="language-plaintext highlighter-rouge">config</code> file, powerlevel9k customizations in <code class="language-plaintext highlighter-rouge">powerlevel9k</code> file and so on, and just <strong>sourced</strong> them into <code class="language-plaintext highlighter-rouge">zshrc</code>. Neat. Dotfiles <code class="language-plaintext highlighter-rouge">zshrc</code> is <em>hardlinked</em> with system’s <code class="language-plaintext highlighter-rouge">zshrc</code>.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Load: Configurations</span> <span class="nb">source</span> <span class="nv">$HOME</span>/dotfiles/zsh/config <span class="c"># Load: Plugins</span> <span class="nb">source</span> <span class="nv">$HOME</span>/dotfiles/zsh/plugins <span class="c"># Load: OH-MY-ZSH</span> <span class="nb">source</span> <span class="nv">$ZSH</span>/oh-my-zsh.sh <span class="c"># Load: POWERLINE</span> <span class="nb">source</span> <span class="nv">$HOME</span>/dotfiles/zsh/powerlevel9k <span class="c"># Load: ALIASES</span> <span class="nb">source</span> <span class="nv">$HOME</span>/dotfiles/zsh/aliases </code></pre></div></div> <p>Looks very simple and clean? But with <strong><code class="language-plaintext highlighter-rouge">source</code></strong> command, it imports and executes a lot of stuff in it and they are loaded into your shell environment before you use them. <code class="language-plaintext highlighter-rouge">zshrc</code> is loaded and executed before you start your terminal. <strong><code class="language-plaintext highlighter-rouge">source</code></strong> is a shell built-in command which is used to read and execute the content of a file, passed as an argument in the current shell script.</p> <h4 id="macos-preferences">macOS Preferences</h4> <p>A lot of macOS settings can directly be set from the command line. Preference and configuration files in macOS use property lists (<em>plists</em>) to specify the attributes, or properties, of an app or process. macOS comes with a <a href="https://support.apple.com/en-in/guide/terminal/apda49a1bb2-577e-4721-8f25-ffc0836f6997/mac"><code class="language-plaintext highlighter-rouge">defaults</code> command-line interface</a> that lets you read, write, and delete macOS user defaults.</p> <p>Mathias Bynens <a href="https://github.com/mathiasbynens/dotfiles/blob/master/.macos">dotfiles repository</a> has a lot of macOS defaults listed, I just picked some of the defaults that I found useful. Here’s how it look like some of them:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Free the Dock</span> defaults write com.apple.Dock size-immutable <span class="nt">-bool</span> no<span class="p">;</span> killall Dock <span class="c"># Remove the auto-hiding Dock delay</span> defaults write com.apple.Dock autohide-delay <span class="nt">-float</span> 0 <span class="c"># Remove the animation when hiding/showing the Dock</span> defaults write com.apple.Dock autohide-time-modifier <span class="nt">-float</span> 0 <span class="c"># Set the icon size of Dock items</span> defaults write com.apple.Dock tilesize <span class="nt">-int</span> 28 <span class="c"># Disable press-and-hold for keys in favor of key repeat</span> defaults write NSGlobalDomain ApplePressAndHoldEnabled <span class="nt">-bool</span> <span class="nb">false</span> </code></pre></div></div> <p>To apply the macOS defaults, you can simply run:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Source ~/.dotfiles/macos/settings </code></pre></div></div> <p>Similarly, I have some of the <a href="https://github.com/jogendra/dotfiles/blob/master/xcode/defaults">defaults</a> specific to Xcode only are inside <a href="https://github.com/jogendra/dotfiles/tree/master/xcode"><code class="language-plaintext highlighter-rouge">xcode/</code></a>.</p> <h4 id="iterm-configurations">iTerm Configurations</h4> <blockquote> <p>iTerm2 + Zsh + Oh-my-zsh = :sparkling_heart:</p> </blockquote> <p>My <a href="https://github.com/jogendra/dotfiles/tree/master/iTerm2"><code class="language-plaintext highlighter-rouge">iTerm2/</code></a> contains <a href="https://github.com/jogendra/dotfiles/blob/master/iTerm2/Default.json"><code class="language-plaintext highlighter-rouge">Default.json</code></a> which is configurations for <em>iTerm2’s</em> <code class="language-plaintext highlighter-rouge">Default</code> profile that includes <em>color schemes</em>, <em>Normal Font</em> (I use <em>JetBrainsMono</em>), <em>Non Ascii Font</em> (I use <em>MesloLGSNer</em>) and lot more. A profile can be imported or can be saved as JSON from <strong><em>iTerm2 &gt; Preferences &gt; Selecting Profile name &gt; Clicking Other Actions</em></strong>.</p> <h4 id="installation-script">Installation Script</h4> <p>To configure another system of your with your remote dotfiles, you need an <em>installation script</em> that does all the installations and syncing process for you.</p> <p>Make sure to check out other people’s scripts for more ideas and inspiration. You can refer to these <a href="https://dotfiles.github.io/utilities/">general-purpose dotfiles utilities</a> that will help you with managing, syncing, and/or installing your dotfiles. Be careful about your installations script, there may already be configurations file like <code class="language-plaintext highlighter-rouge">.zshrc</code>, <code class="language-plaintext highlighter-rouge">.gitconfig</code> etc. and you may not want to lose/replace.</p> <p>My dotfiles follow topological structure. Any file ending in <code class="language-plaintext highlighter-rouge">*.symlink</code> gets symlinked into <code class="language-plaintext highlighter-rouge">$HOME</code>. These get <em>symlinked</em> on running <code class="language-plaintext highlighter-rouge">installers/bootstrap</code>. Any file named <code class="language-plaintext highlighter-rouge">install.sh</code> is executed on running <code class="language-plaintext highlighter-rouge">installers/install</code>. To avoid being loaded automatically, its extension is <code class="language-plaintext highlighter-rouge">.sh</code>, not <code class="language-plaintext highlighter-rouge">.zsh</code>. <a href="https://github.com/jogendra/dotfiles/blob/master/installers/dot"><code class="language-plaintext highlighter-rouge">dot</code></a> is a simple script that installs some dependencies, sets macOS defaults, sets Xcode defaults and so on. Tweak this script, and occasionally run dot from time to time to keep the environment fresh and up-to-date.</p> <p>I will soon be adding <strong>vscode</strong> and <strong>vim</strong> configurations.</p> <h3 id="some-interestinghelpful-reads">Some Interesting/Helpful Reads</h3> <ul> <li><a href="https://dotfiles.github.io/">GitHub ❤ ~/</a></li> <li><a href="http://mywiki.wooledge.org/DotFiles">Configuring your login sessions with dotfiles</a></li> <li><a href="https://shreevatsa.wordpress.com/2008/03/30/zshbash-startup-files-loading-order-bashrc-zshrc-etc/">Zsh/Bash startup files loading order</a></li> <li><a href="https://www.reddit.com/r/linux/comments/at05xh/why_do_hidden_files_in_unix_begin_with_a_dot/egyj6lr/">Why do hidden files in Unix begin with a dot?</a></li> <li><a href="https://0x46.net/thoughts/2019/02/01/dotfile-madness/">Dotfiles madness</a></li> <li><a href="https://opensource.com/article/18/9/shell-dotfile">What a shell dotfile can do for you</a></li> <li><a href="https://www.outcoldman.com/en/archive/2015/09/17/keep-sensitive-data-encrypted-in-dotfiles/">Keep sensitive data encrypted in dotfiles</a></li> <li><a href="https://chrisschuld.com/2019/06/securing-information-in-dotfiles-and-aliases-with-pass/">Securing Information in dotfiles with Password-Store (pass)</a></li> </ul> <p>I hope this post will give you enough push to start with your own dotfiles. Looking forword to your freshly brewed dotfiles. You can reach out to me on <a href="https://www.linkedin.com/in/jogendrasingh24/">LinkedIn</a> in case of anything. Thanks for the read :) Please take care of security of your system (or remote system) and your mental health :sparkles: :sparkles:</p> Wed, 10 Jun 2020 00:00:00 +0000 https://jogendra.dev/i-do-dotfiles https://jogendra.dev/i-do-dotfiles How to use multiple GitHub accounts on single machine <p>Most of us have multiple (mostly two) GitHub accounts, personal and work account. You need to have the ability to push and pull to multiple accounts. This post is about how to setup and use them on a single machine using HTTPS or SSH.</p> <p><strong>Note</strong>: The instructions below have all been executed on macOS and should work fine on all <strong>Unix</strong> based operating systems.</p> <h2 id="using-https">Using HTTPS</h2> <h3 id="step-1-change-remote-url">Step 1: Change remote URL</h3> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote set-url &lt;remote-name&gt; https://&lt;username&gt;@github.com/&lt;username&gt;/&lt;repo-name&gt;.git </code></pre></div></div> <p>If you are setting-up new remote:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote add &lt;remote-name&gt; https://&lt;username&gt;@github.com/&lt;username&gt;/&lt;repo-name&gt;.git </code></pre></div></div> <h3 id="step-2-update-git-config">Step 2: Update git config</h3> <p>By default, git use a system-wide configuration file (global config file) or the one stored at top of your home directory to pick your <code class="language-plaintext highlighter-rouge">username</code> and <code class="language-plaintext highlighter-rouge">email</code>. To ensure that the commits appear as performed by correct username, you have to setup the <code class="language-plaintext highlighter-rouge">user.name</code> and <code class="language-plaintext highlighter-rouge">user.email</code> for project, too:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config user.name &lt;username&gt; git config user.email &lt;[email protected]&gt; </code></pre></div></div> <p>Every git repository has a hidden <code class="language-plaintext highlighter-rouge">.git</code> folder (dotfile) which stores all of git related information. You can always check the username and email for a particular project.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>open .git/config // check <span class="o">[</span>user] section <span class="k">for </span>email and username </code></pre></div></div> <p>You are good to go now!</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git push &lt;remote&gt; &lt;branch&gt; </code></pre></div></div> <p>Entering this, terminal will ask your GitHub account password (once). If it is asking for password each time you push commit, you can cache them using <a href="https://git-scm.com/docs/git-credential-store">git credential store</a>:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config credential.helper store </code></pre></div></div> <blockquote> <p>Using this helper will store your passwords unencrypted on disk, protected only by filesystem permissions. This command stores credentials indefinitely on disk for use by future Git programs.</p> </blockquote> <h2 id="using-ssh">Using SSH</h2> <h3 id="step-1-generate-new-ssh-keys">Step 1: Generate new SSH Keys</h3> <p>Before generating new SSH keys, check for existing SSH keys. To list existing ssh keys, go to the terminal, run command <code class="language-plaintext highlighter-rouge">ls -al ~/.ssh</code>, files with extension <strong>.pub</strong> are your SSH keys. You need to have ssh keys for each account. If you are planning to use two accounts personal and work, there should be two SSH keys, if not, you have to generate them.</p> <p>If you see <em>No such file or directory</em> after running <code class="language-plaintext highlighter-rouge">ls -al ~/.ssh</code> command, go ahead and create <em><strong>~/.ssh</strong></em> directory with command <code class="language-plaintext highlighter-rouge">mkdir -p ~/.ssh</code>.</p> <h4 id="generate-ssh-key-for-work-account">Generate SSH key for Work account</h4> <p>To generate a new SSH key, go to terminal and run the command:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen <span class="nt">-t</span> rsa <span class="nt">-C</span> &lt;work-email&gt; </code></pre></div></div> <p>To get the email associated with your work account:</p> <ol> <li>Go to GitHub</li> <li>Log in to your work account</li> <li>Navigate to <em><strong>Settings</strong></em></li> <li>Copy the associated email</li> </ol> <p>On running the command, you will see:</p> <pre><code class="language-plain">Generating public/private rsa key pair. Enter file in which to save the key (/Users/&lt;current user&gt;/.ssh/id_rsa): </code></pre> <p>Enter file name <code class="language-plaintext highlighter-rouge">/Users/&lt;current-user&gt;/.ssh/id_rsa_work</code>. Default will be <code class="language-plaintext highlighter-rouge">id_rsa</code>. But to differentiate between work and personal, use <strong><em>id_rsa_work</em></strong>_ and <strong><em>id_rsa_personal</em></strong>. After entering the key file name, you will be asked for entering passphrase.</p> <pre><code class="language-plain">Enter passphrase (empty for no passphrase): Enter same passphrase again: </code></pre> <blockquote> <p>A passphrase is similar to a password. The purpose of the passphrase is usually to encrypt the private key. This makes the key file by itself useless to an attacker.</p> </blockquote> <p>Read more about <strong>Passphrase</strong> <a href="https://ssh.com/ssh/passphrase">here</a>.</p> <p>You can just hit <em>enter</em> to skip Passphrase. You will see your generated key and key’s <em>randomart</em> image.</p> <blockquote> <p>The randomart is meant to be an easier way for humans to validate keys. Validation is normally done by a comparison of strings (i.e. the hexadecimal representation of the key fingerprint), which humans are pretty slow and inaccurate at comparing. Randomart replaces this with structured images that are faster and easier to compare.</p> </blockquote> <h4 id="generate-ssh-key-for-personal-account">Generate SSH key for Personal account</h4> <p>Follow the same steps as generating an SSH key for the work account. Save the key file as <strong><em>id_rsa_personal</em></strong>.</p> <p>After generating SSH keys for both work and personal account, if you enter <code class="language-plaintext highlighter-rouge">ls -al ~/.ssh</code>, you will see list of your keys as below:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">-rw-------</span> 1 &lt;current-user&gt; staff 2655 Apr 5 02:18 id_rsa_work <span class="nt">-rw-r--r--</span> 1 &lt;current-user&gt; staff 579 Apr 5 02:18 id_rsa_work.pub <span class="nt">-rw-------</span> 1 &lt;current-user&gt; staff 2655 Apr 5 02:11 id_rsa_personal <span class="nt">-rw-r--r--</span> 1 &lt;current-user&gt; staff 571 Apr 5 02:11 id_rsa_personal.pub </code></pre></div></div> <p><strong><em>id_rsa_work.pub</em></strong> and <strong><em>id_rsa_personal.pub</em></strong> are your public SSH keys. <strong><em>id_rsa_work</em></strong> and <strong><em>id_rsa_personal</em></strong> (keys without <em>.pub</em> extension) are private keys. SSH keys always come in twos. The <strong>private</strong> key is stored on the client (your machine). The <strong>public</strong> key is stored on the remote machine. When you makes an attempt to connect to a remote machine (GitHub here but true in general) via SSH, the SSH protocol will check your computer for the private key that matches the public key stored on the remote machine. If they matches, the connection is successful.</p> <h3 id="step-2-add-generated-ssh-keys-to-github-accounts">Step 2: Add generated SSH Keys to GitHub accounts</h3> <h4 id="for-work-account">For Work account</h4> <p>Copy your work account SSh key to machine clipboard with command:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pbcopy &lt; ~/.ssh/id_rsa_work.pub </code></pre></div></div> <p>To associate SSH key with GitHub account:</p> <ol> <li>Go to GitHub</li> <li>Login to your work account</li> <li>Navigate to <strong><em>Settings</em></strong></li> <li>Go to <strong><em>SSH and GPG Keys</em></strong> section from the sidebar</li> <li>Click on <strong>New SSH Key</strong> button</li> <li>Paste key in <strong><em>Key</em></strong> text field.</li> </ol> <p>You will most likely receive a mail from GitHub that a new public key is added to your account.</p> <h4 id="for-personal-account">For Personal account</h4> <p>Use key <strong><em>id_rsa_personal</em></strong> and follow the same step as work account for personal account.</p> <h3 id="step-3-update-git-global-configuration">Step 3: Update git global configuration</h3> <p><em>Ignore this step if you are going for <strong>Step 4</strong></em></p> <p>Git global configuration file should be aware of all of your GitHub accounts. Run this command to edit the global config file:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config <span class="nt">--global</span> <span class="nt">--edit</span> </code></pre></div></div> <p>Config file will open in <strong>vim</strong>, enter <code class="language-plaintext highlighter-rouge">i</code> for insert mode and enter:</p> <pre><code class="language-plain">[work] name = &lt;work github username&gt; email = &lt;work email&gt; [personal] name = &lt;personal github username&gt; email = &lt;personal email&gt; </code></pre> <p>Press <code class="language-plaintext highlighter-rouge">esc</code> and <code class="language-plaintext highlighter-rouge">:wq</code> to exit the insert mode in vim.</p> <p>Now, whenever you will be pulling or pushing, you will be asked which account should be used for this repositopry (once only) and the local <em>config file</em> (repository level) will remember the account.</p> <p><strong>Important</strong>: Make sure you’re using SSH instead of HTTPS as remote URL:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote set-url &lt;remote-name&gt; [email protected]:&lt;username&gt;/&lt;repo-name&gt;.git </code></pre></div></div> <p>If you are setting-up new remote:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote add &lt;remote-name&gt; [email protected]:&lt;username&gt;/&lt;repo-name&gt;.git </code></pre></div></div> <p>Now, you are all set to use multiple GitHub accounts on your machine!</p> <p>Further, feel free to use SSH configuration rules explanined below if you find them more handy.</p> <h3 id="step-4-add-ssh-configuration-rules-optional">Step 4: Add SSH configuration rules [OPTIONAL]</h3> <h4 id="register-new-ssh-keys">Register new SSH keys</h4> <p>Before configuring rules, register the new SSH Keys with the ssh-agent. Open the terminal and run:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-add ~/.ssh/id_rsa_work ssh-add ~/.ssh/id_rsa_personal </code></pre></div></div> <p>If you entered passphrase during generating key process, you will be asked to enter the same here, not otherwise.</p> <pre><code class="language-plain">Enter passphrase for /Users/&lt;current-user&gt;/.ssh/id_rsa_work: </code></pre> <p><em>(same for personal account key)</em></p> <p>On success you will see:</p> <pre><code class="language-plain">Identity added: /Users/&lt;current-user&gt;/.ssh/id_rsa_work (&lt;work-email&gt;) </code></pre> <p><em>(same for personal account key)</em></p> <p>To make sure if ssh-agent is running, run command <code class="language-plaintext highlighter-rouge">eval "$(ssh-agent -s)"</code>.</p> <h4 id="create-ssh-config-file-and-add-rules">Create SSH Config File and Add Rules</h4> <p>Create new SSH config file by runnning</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">touch</span> ~/.ssh/config </code></pre></div></div> <p>We need to add SSH config rules for different hosts to specify which identity file to use for which domain. This is way to tell SSH when to use work account and when to use personal account while performing git/github related stuff.</p> <p>Open newly created config file with command <code class="language-plaintext highlighter-rouge">vim ~/.ssh/config</code>. You can choose other editor to edit file if you are not comfortable with vim.</p> <p>Add rules to config file as below:</p> <pre><code class="language-plain"># Rule for Work account Host github.com-work HostName github.com User git IdentityFile ~/.ssh/id_rsa_work # Rule for Personal account Host github.com-personal HostName github.com User git IdentityFile ~/.ssh/id_rsa_personal </code></pre> <h4 id="test-the-rules">Test the rules</h4> <p>To check if everything is working fine, create a new test repository on your personal GitHub account and run commands below in terminal:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir </span>TestRepo <span class="nb">cd </span>TestRepo git init <span class="nb">touch </span>testfile.txt <span class="nb">echo</span> <span class="s2">"This is test line"</span> <span class="o">&gt;&gt;</span> testfile.txt git add testfile.txt git commit <span class="nt">-m</span> <span class="s2">"This is test commit"</span> git remote add origin git@github-personal:&lt;personal-username&gt;/TestRepo.git git push origin master </code></pre></div></div> <h4 id="things-to-be-taken-care">Things to be taken care</h4> <ul> <li>If you have the repository already cloned update remote URLs like below:</li> </ul> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote set-url &lt;remote-name&gt; [email protected]:&lt;username&gt;/&lt;repo-name&gt;.git </code></pre></div></div> <ul> <li>If you are creating a new repository set remote like below:</li> </ul> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote add &lt;remote-name&gt; [email protected]:&lt;username&gt;/&lt;repo-name&gt;.git </code></pre></div></div> <ul> <li>If you are cloning a repository</li> </ul> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone [email protected]:&lt;username&gt;/&lt;repo-name&gt;.git </code></pre></div></div> <p><em>(same for work repositories, use github.com-work)</em></p> <p>All set, rest will be taken care by SSH configuration!</p> <p>Don’t forget to share ^_^</p> Sun, 05 Apr 2020 00:00:00 +0000 https://jogendra.dev/how-to-use-multiple-github-accounts-on-single-machine https://jogendra.dev/how-to-use-multiple-github-accounts-on-single-machine How to write proposal for Google Summer of Code <p>It worth mentioning that the proposal plays a very big role in selecting in Google Summer of Code. A proposal is basically you describing to your organisation how you would go about the project. Based on this and your credibility, your organisation would contemplate on whether to select you or not. I was Google Summer of Code student in 2018. During the proposal submitting time, I have seen a lot of proposal on the organization chat channels, some are good and some bad. Many people try but some of them get in as there are limited slots in every organization. What stands out the selected student from the student who didn’t get selected is their proposals (there are other factors too). Your proposal should convince the mentors that you’re the right person for the project that can take project to next level.</p> <p>I did Google Summer of Code 2018 with FOSSASIA and Mentored Google Summer of Code 2019 students. I am sharing my perspective as a mentor, what mentor except in a proposal and what are some things that make you stand out.</p> <h3 id="what-a-good-proposal-contain">What a good proposal contain?</h3> <h4 id="project-description">Project Description</h4> <p>Write a small description of the project. It makes sense when you are proposing a new project. Do not copy the project description from the project GitHub page and paste.</p> <h4 id="deliverables">Deliverables</h4> <p>You should propose a clear list of deliverables, explaining exactly what you promise to do and what you do not plan to do. You can also include the list of libraries/frameworks/tools which you are planning use for deliverables to make things clear.</p> <h4 id="implementation">Implementation</h4> <p>Implementation is the detailed section of Deliverables. Describe each deliverable in detail, how you are planning to get deliverables done. Write little about the technical things like mentioning libraries/frameworks/tools or including small code snippet or wireframe.</p> <h4 id="timeline">Timeline</h4> <p>In this section, break google summer of code timeline into the small time interval right from the Community Bonding Period right to the Final Submission date and write about what you will be delivered in particular time interval. Be realistic about the timeline, mentors can easily spot unrealistic timelines. You can split the time up into two week periods and described what I would do in each those periods. Find the full program timeline <a href="https://summerofcode.withgoogle.com/how-it-works/#timeline">here</a>.</p> <h4 id="open-source-contributions">Open Source Contributions</h4> <p>It is also important to mention how much you have contributed to the organization or Open Source in general. You can give links to the Pull Requests you have made or issued you have opened.</p> <h4 id="personal-information">Personal Information</h4> <p>Write some important information about yourself. I have mentioned the following info in the proposal:</p> <ul> <li>Name</li> <li>University</li> <li>Email</li> <li>Personal Website</li> <li>Github Handle</li> <li>LinkedIn Profile Link</li> <li>Resume Link</li> <li>Location</li> <li>Timezone</li> </ul> <h4 id="about-me">About Me</h4> <p>Write down a few sentences about yourself like your coding background, coding projects, internship experience etc which show that you are the strong candidate. Showing that you have already worked on a similar project helps a lot.</p> <h4 id="obligationsschedule-conflicts">Obligations/Schedule Conflicts</h4> <p>Mention about the obligations you will be having during the GSoC period, mention the number of hours in a week you can give to your GSoC project. Mentor understand that you have you may have university classes/exams, they will not mind but mention it to avoid future inconvenience. Most likely many students have exams in the initial period and new semester starting in the final period.</p> <h4 id="post-gsoc-and-future-work">Post GSoC and Future Work</h4> <p>Write a little about the work that will not be delivered during the GSoC period due to the time limit but after GSoC. Mention about how you will be making contributions to the organization after GSoC (coding is not the only thing that matters to organizations).</p> <h4 id="references">References</h4> <p>Provide links to the sources you have used in the proposal, it is always a good idea.</p> <h4 id="pro-tips">PRO TIPS</h4> <p><em>that’s what you need to know to stand out!</em></p> <ul> <li><strong>Get your proposal reviewed</strong> - It is important to get reviewed your proposal from the project mentor or right person in the organization. Organizations are always looking for the best students, they are always ready to review your proposal. The mentor can tell you what exactly the organization is willing to get done from a particular project in GSoC. You can get the review from the other participating students, friend/senior of you who have done google summer of code in the past. I would suggest you make proposal in a google doc which you can edit anytime. Make sure comments are enabled on the doc. Share the document link on the organization mailing list/channel and ask others to review. You can ping personally too.</li> <li><strong>Check the Grammar before submitting</strong> - Mentors know that students come from very different places and it is not necessary to be proposal in good English, no mentor will mind that but check your punctuation and use a spellchecker like <a href="https://grammarly.com/">Grammarly</a>.</li> <li><strong>Submit your draft proposal early</strong> - Submit your draft proposal early so that mentor can give their feedback before final submission and ask you questions or request more detail on aspects of your proposal. Google allows you to edit the draft as many times as you wish before the application deadline.</li> <li><strong>Quality &gt; Quantity</strong> - You can submit up to three proposals to one organization or different organizations, it’s up to you but I would suggest you to submit less proposals but make sure they are the best.</li> <li><strong>Describe your proposal, not project</strong> - Use the abstract section to briefly describe your proposal ideas than the project itself. The project in GSoC sense is the actual work you will be doing and not the organization project. Your mentor know very well what your project is.</li> <li><strong>Be proactive</strong> - Mentors are more likely to select students that openly discuss the existing ideas and/or propose their own. It is a bad idea to just submit your idea only on the Google web site without discussing it because it won’t be noticed.</li> <li><strong>Do not sit back after submitting the proposal</strong> - Keep working on the bug fixes, this could influence their decision. Try to learn more about the codebase. Selecting or not selecting is in organization mentor hand but keep working, that’s what is in your hand.</li> <li><strong>We’re long-term people looking for long-term people</strong> - Students that are likely to disappear after GSoC are less likely to be selected. This is because there is no point in developing something that won’t be maintained. And moreover, one scope of GSoC is to bring new developers to the community. Show them that you are long-term people.</li> <li><strong>Keep it short</strong> - Do NOT make a very lengthy proposal, mentors have limited time and many proposals to review. Keep your proposal short and informative. Your lengthy proposal will look like filling space with noise. Keep your proposal DRY (Don’t Repeat Yourself)</li> </ul> <p>Here are my proposals that I submitted to the FOSSASIA organization:</p> <ol> <li><a href="https://docs.google.com/document/d/1iR7-LCF6rbTaZglPxOOwNPBTvtXuIZwWJiQMlSYlOrY/edit?usp=sharing">Enhancement to SUSI iOS Project</a> (Accepted)</li> <li><a href="https://docs.google.com/document/d/1emFcE0cMTButYEzoKZGScOVEPHHqMv6_mrOIwUxmJkA/edit?usp=sharing">Phimpme iOS Project</a> (Not Accepted - to be honest, I gave a lot more time making this proposal and did good research than the accepted proposal above. It was a new proposed project. You can see how much it is important to know about the priority of the project for you are going to submit the proposal. Pro tips: contact the mentor earlier, they can tell you what are their priorities and what exactly they are looking for!)</li> </ol> <h3 id="important-links">Important Links</h3> <ul> <li><a href="https://summerofcode.withgoogle.com/">Summer of Code official website - everything you need to know is here</a></li> <li><a href="https://google.github.io/gsocguides/student/writing-a-proposal">Writing a proposal - a guide by google</a></li> <li><a href="https://github.com/saketkc/fos-proposals">Archive of GSoC proposals</a></li> </ul> <h3 id="closing-notes">Closing Notes</h3> <p>I hope I have provided useful information. Wish you all the best for the Google Summer of Code. Do NOT get demotivated in case your proposal didn’t get selected, Keep doing open source, I am sure you are gonna learn a lot. After all Open Source is Love. In case of any query, you can always reach out to me on <a href="https://twitter.com/jogendrafx">twitter</a> OR contact information given on <a href="/about">About Me</a> page. You can find my <a href="https://github.com/jogendra">GitHub Profile here</a>.</p> Mon, 25 Feb 2019 00:00:00 +0000 https://jogendra.dev/how-to-write-proposal-for-google-summer-of-code https://jogendra.dev/how-to-write-proposal-for-google-summer-of-code My Google Summer of Code Project Blogs <p>I was Google Summer of Code (2018) Student Developer for <a href="https://fossasia.org/">FOSSASIA</a>. I spend summer working on <a href="https://github.com/fossasia/susi_iOS">SUSI.AI iOS</a> project. During GSoC period, I have written weekly blogs about the features implemented by me. You can find all the blogs in links below. These blogs are originally published on <a href="https://blog.fossasia.org/author/imjog/">FOSSASIA Blogs</a> website.</p> <ul> <li><a href="https://blog.fossasia.org/connecting-susi-ios-app-to-susi-smart-speaker/">Connecting SUSI iOS App to SUSI Smart Speaker</a> - August 25, 2018</li> <li><a href="https://blog.fossasia.org/adding-support-for-playing-youtube-videos-in-susi-ios-app/">Adding Support for Playing Youtube Videos in SUSI iOS App</a> - August 21, 2018</li> <li><a href="https://blog.fossasia.org/adding-option-to-choose-room-for-susi-smart-speaker-in-ios-app/">Adding Option to Choose Room for SUSI Smart Speaker in iOS App</a> - August 21, 2018</li> <li><a href="https://blog.fossasia.org/adding-support-for-playing-audio-in-susi-ios-app/">Adding Support for Playing Audio in SUSI iOS App</a> - August 21, 2018</li> <li><a href="https://blog.fossasia.org/integrating-gravatar-and-anonymizing-email-address-in-feedback-section-in-susi-ai-ios-app/">Integrating Gravatar and Anonymizing Email Address in Feedback Section</a> - August 20, 2018</li> <li><a href="https://blog.fossasia.org/implementing-five-star-rating-ui-in-susi-ios/">Implementing Five Star Rating UI in SUSI iOS</a> - August 4, 2018</li> <li><a href="https://blog.fossasia.org/adding-support-for-displaying-images-in-susi-ios/">Adding Support for Displaying Images in SUSI iOS</a> - July 29, 2018</li> <li><a href="https://blog.fossasia.org/displaying-skills-feedback-on-susi-ios/">Displaying Skills Feedback on SUSI iOS</a> - July 28, 2018</li> <li><a href="https://blog.fossasia.org/allowing-user-to-submit-ratings-for-skills-in-susi-ios/">Allowing user to submit ratings for skills in SUSI iOS</a> - July 25, 2018</li> <li><a href="https://blog.fossasia.org/change-text-to-speech-voice-language-of-susi-in-susi-ios/">Change Text-to-Speech Voice Language of SUSI in SUSI iOS</a> - July 24, 2018</li> <li><a href="https://blog.fossasia.org/stop-action-implementation-in-susi-ios/">STOP action implementation in SUSI iOS</a> - July 21, 2018</li> <li><a href="https://blog.fossasia.org/how-anonymous-mode-is-implemented-in-susi-ios/">How Anonymous Mode is Implemented in SUSI iOS</a> - July 15, 2018</li> <li><a href="https://blog.fossasia.org/initial-setups-for-connecting-susi-smart-speaker-with-iphone-ipad/">Initial Setups for Connecting SUSI Smart Speaker with iPhone/iPad</a> - June 29, 2018</li> <li><a href="https://blog.fossasia.org/post-feedback-for-susi-skills-in-susi-ios/">Post feedback for SUSI Skills in SUSI iOS</a> - June 27, 2018</li> <li><a href="https://blog.fossasia.org/creating-onboarding-screens-for-susi-ios/">Creating Onboarding Screens for SUSI iOS</a> - June 8, 2018</li> </ul> <p><strong>Closing Notes</strong></p> <p>Initially it was hard writing blogs but after writing some blogs, it’s got easier. Google Summer of Code is not all about the code, it is about communication, team work, documentations, scrums, meetups. The GSoC journey was fun indeed. If you have any query regarding my GSoC project or GSoC in general, feel free to reach me out :)</p> Sat, 23 Feb 2019 00:00:00 +0000 https://jogendra.dev/my-google-summer-of-code-blogs https://jogendra.dev/my-google-summer-of-code-blogs Changing My GitHub Username <p>Now I have my first name as my GitHub username!</p> <p>My old username was <strong>imjog</strong> so I wanted to change it to my first name <strong>jogendra</strong> but this username was not available (Already taken by someone).</p> <p>I looked into <strong>jogendra</strong> profile and found that it was inactive since this profile created (Zero activity). Then I came across Github’s <a href="https://help.github.com/articles/name-squatting-policy/">username squatting policy</a>:</p> <blockquote> <p>GitHub account names are provided on a first-come, first-served basis, and are intended for immediate and active use. Account names may not be inactively held for future use. GitHub account name squatting is prohibited. Inactive accounts may be renamed or removed by GitHub staff at their discretion. Keep in mind that not all activity on GitHub is publicly visible. Staff will not remove or rename any active account.</p> </blockquote> <p>Immediately clicked the <strong>Contact a human</strong> button on the page and sent them this request to release jogendra username:</p> <blockquote> <p>Hi,</p> <p>I’d like to know if the username <code class="language-plaintext highlighter-rouge">jogendra</code> can be released? The user who has registered that username doesn’t seem to have any activity at all (from 2013).</p> <p>My first name is Jogendra so I’d like to have <code class="language-plaintext highlighter-rouge">jogendra</code> as my username. Currently, I own <code class="language-plaintext highlighter-rouge">imjog</code>.</p> <p>Please let me know if this is possible. Would be really awesome if it can be done!</p> <p>Cheers, Jogendra</p> </blockquote> <p>GitHub customer service is amazing, I got the reply email from GitHub within 2 hours.</p> <blockquote> <p>Hey Jogendra,</p> <p>You are in luck — we have classified the jogendra account as inactive and released the username for you to claim, as per our Name Squatting Policy:</p> <p>https://help.github.com/</p> <p>articles/name-squatting-policy</p> <p>Be quick, as the username is now publicly available!</p> <p>Cheers, Clark</p> </blockquote> <p>They released my requested username. Finally, I changed my old username <code class="language-plaintext highlighter-rouge">imjog</code> to <code class="language-plaintext highlighter-rouge">jogendra</code>.</p> <p>Now, I have to update my GitHub profile URL wherever I had given and change Remote URLs of local repositories.</p> <p>Find me on GitHub with my new username here: <a href="https://github.com/jogendra">https://github.com/jogendra</a></p> <p>If you ever want to change your GitHub username, <a href="https://nikhita.github.io/changing-my-github-username">this post</a> by <a href="https://github.com/nikhita">Nikhita</a> is very helpful. In fact, I was inspired by this post!</p> Wed, 05 Sep 2018 00:00:00 +0000 https://jogendra.dev/changing-my-github-username https://jogendra.dev/changing-my-github-username