<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Posts on tkainrad</title>
    <link>https://tkainrad.dev/posts/</link>
    <description>Recent content in Posts on tkainrad</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Thu, 08 Apr 2021 00:00:00 +0000</lastBuildDate>
    
        <atom:link href="https://tkainrad.dev/posts/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Why Keyboard Shortcuts don&#39;t work on non-US Layouts and how Devs could fix it</title>
      <link>https://tkainrad.dev/posts/why-keyboard-shortcuts-dont-work-on-non-us-keyboard-layouts-and-how-to-fix-it/</link>
      <pubDate>Thu, 08 Apr 2021 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/why-keyboard-shortcuts-dont-work-on-non-us-keyboard-layouts-and-how-to-fix-it/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;During the past 15 months, I have been thinking a lot about keyboard shortcuts and about how different applications handle them. I shouldn&amp;rsquo;t complain; I knew what I was getting into when I started to build &lt;a href=&#34;https://keycombiner.com/&#34;&gt;KeyCombiner&lt;/a&gt;, an app for learning and looking up shortcuts and text snippets. Still, I didn&amp;rsquo;t quite know how much of a mess keyboard shortcut handling on the web is.&lt;/p&gt;
&lt;p&gt;Do you use an international keyboard layout? Then you already know what I am talking about. You probably have had some issues typing keyboard shortcuts such as &lt;kbd&gt;alt&lt;/kbd&gt;+&lt;kbd&gt;/&lt;/kbd&gt;, or &lt;kbd&gt;cmd&lt;/kbd&gt;+&lt;kbd&gt;[&lt;/kbd&gt;. This post will help you to understand why this annoyance exists. However, the people who should most urgently read this post are web developers. Are you a web developer? Great! I will explain how you are currently letting down non-US users and how you can get us out of this mess.&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;I will focus on web applications. The behavior on desktop apps is often different. However, with the rise of Electron apps, web technologies are ever-present on the desktop as well. So, the arguments in this article probably apply to half of your installed apps as well.&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;examples-of-broken-apps&#34;&gt;Examples of Broken Apps&lt;/h1&gt;
&lt;p&gt;The problem I am talking about is so incredibly omnipresent that most international users are just used to the fact that they cannot type a good portion of a given web application&amp;rsquo;s keyboard shortcuts on their keyboards. However, web applications are becoming increasingly complex, and they are rapidly replacing desktop applications, making this problem bigger by the day.&lt;/p&gt;
&lt;p&gt;This is most annoying when the most important keyboard shortcuts are inaccessible. A very common shortcut is &lt;kbd&gt;/&lt;/kbd&gt; for accessing search functionality. Unfortunately, there is no &lt;kbd&gt;/&lt;/kbd&gt;-key on most international layouts. Adding modifiers to produce this key with your layout rarely helps. For example, on my German layout, &lt;kbd&gt;/&lt;/kbd&gt; is produced via &lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;7&lt;/kbd&gt;. Most web applications will ignore this.
Similarly painful is when Electron apps use &lt;kbd&gt;[&lt;/kbd&gt; and &lt;kbd&gt;]&lt;/kbd&gt; for navigating backwards and forwards.&lt;/p&gt;
&lt;p&gt;If you use a US layout, you might be surprised to hear about these problems. But rest assured, they are not new and I am not the only one who is affected.
We are at a point where it is easy to find users complaining about this for almost any popular web application.&lt;/p&gt;
&lt;p&gt;An interesting example are Google&amp;rsquo;s apps. For some reason, Google manages to handle international layouts correctly in Gmail, but fails to do so in any of its other applications. The web is full of complaints, e.g. for &lt;a href=&#34;https://webapps.stackexchange.com/questions/116405/google-spreadsheet-equivalent-of-excels-ctrlsemicolon-on-international-keyboar&#34;&gt;Google Sheets&lt;/a&gt;, &lt;a href=&#34;https://webapps.stackexchange.com/questions/114185/google-drive-keyboard-shortcuts-not-working-with-non-us-keyboard&#34;&gt;Google Drive&lt;/a&gt;, and &lt;a href=&#34;https://superuser.com/questions/217082/why-dont-the-google-docs-ctrl-alt-shortcuts-work&#34;&gt;Google Docs&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;Update: International Layouts now work in Google Chat. Interestingly, the fix was applied shortly after this article &lt;a href=&#34;https://tkainrad.dev/posts/why-keyboard-shortcuts-dont-work-on-non-us-keyboard-layouts-and-how-to-fix-it/&#34;&gt;reached the front page of Hacker News&lt;/a&gt;. I am not saying that these events are correlated, but it is a nice thought.&lt;br&gt;
Unfortunately, the problem persists for other Google Apps, such as GDrive.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;Another, almost funny examply is Figma, the popular web-based vector-graphics editor. So many people &lt;a href=&#34;https://spectrum.chat/figma/general/shortcuts-on-german-keyboard~972dac03-c1c6-4dc0-9233-020b2bfac038&#34;&gt;complained&lt;/a&gt; that there is now a &lt;a href=&#34;https://www.figma.com/proto/q2OVHiUu6hdDiOZLzDmUL9/Shortcuts-(International)?scaling=contain&amp;amp;node-id=10%3A234&#34;&gt;dedicated site with workarounds for different layouts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://forum.asana.com/t/support-for-keyboard-shortcuts-with-international-layouts/15430&#34;&gt;This complaint in the Asana forums&lt;/a&gt; is funny too. They made some specific adjustments for some layouts, only to give up in the end and say that they will still not support more exotic layouts.&lt;/p&gt;
&lt;p&gt;There are also plenty of complaints for Notion, see &lt;a href=&#34;https://twitter.com/NotionHQ/status/997602351740432384&#34;&gt;here&lt;/a&gt; or &lt;a href=&#34;https://www.reddit.com/r/Notion/comments/fi6cis/german_qwertz_keyboard_hack_to_remap/&#34;&gt;here&lt;/a&gt;. The competition doesn&amp;rsquo;t do much better, though, &lt;a href=&#34;https://github.com/Roam-Research/issues/issues/131&#34;&gt;here is an open issue for Roam Research&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;Finding all these applications with broken shortcut handling was very easy. I used &lt;a href=&#34;https://keycombiner.com/collecting/collections/public/search/?description=&amp;amp;keys=%2F&amp;amp;mac_keys=&amp;amp;submit=Search&#34;&gt;KeyCombiner&amp;rsquo;s shortcut search to find all applications that use the US &lt;kbd&gt;/&lt;/kbd&gt;-key in their shortcuts&lt;/a&gt;. Then I googled and found complaints for most of these apps. In the case of Notion and Google Drive/Docs/Sheets, I was already aware simply because I use these applications myself.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;I want to be clear, broken keyboard shortcuts are not a law of nature. It is possible to implement web application shortcuts so that they can be typed with any layout. It shouldn&amp;rsquo;t be an excuse that even some of the most popular apps do not get it right.&lt;/p&gt;
&lt;h1 id=&#34;why-is-this-happening&#34;&gt;Why is this happening?&lt;/h1&gt;
&lt;p&gt;What baffles me is that the problem exists in apps used by millions of people every day, developed by gigantic corporations who pride themselves with having the best engineers in the world. Why is this baffling? Because the underlying technical issues are rather trivial!&lt;/p&gt;
&lt;p&gt;Web applications use JavaScript to process keyboard shortcuts. To do that, they listen to keyboard events that are emitted by your browser whenever your press a key. That&amp;rsquo;s where it gets a little messy. These keyboard events have several properties that can be used to identify which keys have been pressed. To make matters worse, three different events are associated with a single button press: &lt;code&gt;keydown&lt;/code&gt; &lt;code&gt;keypress&lt;/code&gt; &lt;code&gt;keyup&lt;/code&gt;. Most applications use a Javascript library that handles all these things for them, primarily &lt;a href=&#34;https://github.com/jaywcjlove/hotkeys&#34;&gt;HotKeys.js&lt;/a&gt; or &lt;a href=&#34;https://github.com/ccampbell/mousetrap&#34;&gt;Mousetrap&lt;/a&gt;. Let&amp;rsquo;s look at &lt;a href=&#34;https://github.com/jaywcjlove/hotkeys/blob/8b398e9c24c462f06e4d2acb47a5403295eaabcf/dist/hotkeys.js#L358&#34;&gt;what Hotkeys.js does&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;let&lt;/span&gt; key &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; event.keyCode &lt;span style=&#34;color:#555&#34;&gt;||&lt;/span&gt; event.which &lt;span style=&#34;color:#555&#34;&gt;||&lt;/span&gt; event.charCode;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It uses &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode&#34;&gt;&lt;code&gt;keyCode&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/which&#34;&gt;&lt;code&gt;which&lt;/code&gt;&lt;/a&gt;, or &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/charCode&#34;&gt;&lt;code&gt;charCode&lt;/code&gt;&lt;/a&gt; to find out which key was pressed. Why is this a problem? For starters, all three properties are deprecated. That isn&amp;rsquo;t even the main problem though. These event properties are simply not suited for finding out which character was pressed, at least when used with &lt;code&gt;keydown&lt;/code&gt; or &lt;code&gt;keyup&lt;/code&gt;. Still a vast majority of web applications use the &lt;code&gt;keydown&lt;/code&gt; evente with these properties.
It is very clearly documented that this should not be done and that it is not compatible across layouts, operating systems, and even browsers. To cite the MDN web docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Web developers shouldn&amp;rsquo;t use the keyCode attribute for printable characters when handling keydown and keyup events. As described above, the keyCode attribute is not useful for printable characters, especially those input with the Shift or Alt key pressed. When implementing a shortcut key handler, the keypress event is usually better.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Using Chrome on Linux with a german keyboard, my &lt;kbd&gt;#&lt;/kbd&gt; key will produce the required &lt;code&gt;keyCode&lt;/code&gt; for triggering &lt;kbd&gt;/&lt;/kbd&gt; shortcuts. However, this does not work on Firefox, and neither does it work on Chrome on macOS. With many browser and OS combinations, there is no way to trigger this shortcut on a german layout. The same goes for many other international keyboard layouts that do not have a dedicated &lt;kbd&gt;/&lt;/kbd&gt;-key.&lt;/p&gt;
&lt;p&gt;If you want to see which button presses produce which event properties, you can use this neat little web tool:
&lt;a href=&#34;https://w3c.github.io/uievents/tools/key-event-viewer.html&#34;&gt;https://w3c.github.io/uievents/tools/key-event-viewer.html&lt;/a&gt;&lt;br&gt;
Hint for developers: Switch your computer&amp;rsquo;s keyboard layout to something other than US to debug your application&amp;rsquo;s shortcut handling on different layouts.&lt;/p&gt;
&lt;p&gt;Using this event viewer, you will see that the &lt;code&gt;keypress&lt;/code&gt; event usually produces the correct code on any layout. Unfortunately, this event is deprecated as a whole and comes with its own problems. Still, the &lt;a href=&#34;https://github.com/ccampbell/mousetrap&#34;&gt;Mousetrap&lt;/a&gt; library can use the &lt;code&gt;keypress&lt;/code&gt; event with fairly good results on international layouts.&lt;/p&gt;
&lt;p&gt;As you can see, the situation is messy. However, developers are usually pretty good at dealing with complex problems. In the next section, I will point out some straightforward ways to do better than Google and other tech giants.&lt;/p&gt;
&lt;h1 id=&#34;how-you-can-do-better&#34;&gt;How you can do better&lt;/h1&gt;
&lt;p&gt;The good news is that this problem is easy to fix. Depending on your resources and the technical and semantical requirements of your application, there are a few things that you could do:&lt;/p&gt;
&lt;h2 id=&#34;simple-workarounds&#34;&gt;Simple Workarounds&lt;/h2&gt;
&lt;p&gt;If your web application does not have a huge amount of shortcuts, or if you don&amp;rsquo;t have the resources to spend a meaningful amount of engineering work on this problem, here are some trivial ways to work around the described issues.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do not use special characters for your keyboard shortcuts.&lt;/strong&gt;&lt;br&gt;
That&amp;rsquo;s all you would need to do to make 99% of your international users happy. If you want to go one step further, you can try to stay on the home row or choose otherwise easy-to-type characters. In case you have an existing application with shortcuts that cannot be changed because your American users already learned them, consider the suggestion below.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Provide Alternatives&lt;/strong&gt;&lt;br&gt;
If you really do need to use special characters, provide alternatives that can be typed on all keyboard layouts. Both HotKeys.js and Mousetrap support multiple triggers when creating your bindings.
For example if you have &lt;kbd&gt;/&lt;/kbd&gt; as you shortcut for focusing the search field because your US users are accustomed to this behavior, just add &lt;kbd&gt;alt&lt;/kbd&gt;+&lt;kbd&gt;s&lt;/kbd&gt; as an alternative for international users. Some US users might welcome this change as well, because &lt;kbd&gt;alt&lt;/kbd&gt;+&lt;kbd&gt;s&lt;/kbd&gt; can be typed with just the left hand while &lt;kbd&gt;/&lt;/kbd&gt; reqiures people to take their hand of the mouse.&lt;/p&gt;
&lt;h2 id=&#34;technical-fixes&#34;&gt;Technical Fixes&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Actually make the shortcuts work with any keyboard layout.&lt;/strong&gt;&lt;br&gt;
This means when the key combination is &lt;kbd&gt;alt&lt;/kbd&gt;+&lt;kbd&gt;/&lt;/kbd&gt;, the shortcut should be triggered when a german layout user presses &lt;kbd&gt;alt&lt;/kbd&gt;+&lt;kbd&gt;shift&lt;/kbd&gt;+&lt;kbd&gt;7&lt;/kbd&gt;. This is surprisingly easy with some of the popular shortcut processing libraries. With Mousetrap, all you need to do is listen to the &lt;code&gt;keypress&lt;/code&gt; events instead of &lt;code&gt;keydown&lt;/code&gt;. In the next section of this post, I will mention some applications that work this way.
Unfortunately, there is a minor downside to this approach. It can make some shortcuts tedious to type on some layouts, e.g. requiring two or three modifiers. It also has some potential for shortcut conflicts. &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;/&lt;/kbd&gt; is suddenly the same as &lt;kbd&gt;ctrl&lt;/kbd&gt;+&lt;kbd&gt;shift&lt;/kbd&gt;+&lt;kbd&gt;7&lt;/kbd&gt;. You should be aware of that and not assign these things to two different bindings. The easiest way to avoid such conflicts is by staying away from the &lt;kbd&gt;Shift&lt;/kbd&gt; modifier when defining your bindings.&lt;/p&gt;
&lt;p&gt;If you want to go all the way, you could write your own shortcut processing code, and possibly make it into an open-source library. The current alternatives do not care too much about supporting different layouts.
There are a lot of things one could do to solve all the issues described in this article. For example, you could use the &lt;code&gt;key&lt;/code&gt; attribute, or even play around with the experimental &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Keyboard/getLayoutMap&#34;&gt;&lt;code&gt;keyboard.getLayoutMap&lt;/code&gt; API&lt;/a&gt;. KeyCombiner uses this API to display its &lt;a href=&#34;https://tkainrad.dev/posts/visualize-collections-of-keyboard-shortcuts/&#34;&gt;on-screen virtual keyboards&lt;/a&gt; with the user&amp;rsquo;s layout.
Sometimes, I think about making such a library myself. KeyCombiner already uses a fork of Mousetrap that has been customized quite a bit. However, I already have a demanding full-time job and a large side-project, so I would be happy if you beat me to it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Allow users to change the default key bindings.&lt;/strong&gt;&lt;br&gt;
This way, users of international keyboard layouts, can change invalid bindings to something that works for them. As a bonus, you would make any shortcut enthusiast happy, and your marketing people could point out how all your sorry-ass competitors do not allow custom keyboard shortcuts.&lt;/p&gt;
&lt;p&gt;Interestingly, I am not aware of a single web application that lets you customize keyboard shortcuts, even though it wouldn&amp;rsquo;t be that difficult to implement, especially with modern SPAs that would have to pull the custom bindings from the database only once. You could store them in your database, pull them out when a user logs in, and pass them to your shortcut processing library.&lt;/p&gt;
&lt;h1 id=&#34;wall-of-fame&#34;&gt;Wall of Fame&lt;/h1&gt;
&lt;p&gt;Fortunately, some applications already do it right. Three apps that I am using where my german layout works flawlessly are Gmail, Linear, and Discourse.&lt;/p&gt;
&lt;h2 id=&#34;gmail&#34;&gt;Gmail&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s hard to believe that Gmail is created by the same company as Google Drive, Google Chat, and many other apps that do not handle shortcuts correctly on international layouts. The Gmail web interface works great on all layouts that I tested. On my german layout, I can even use &lt;kbd&gt;AltGr&lt;/kbd&gt;+&lt;kbd&gt;8&lt;/kbd&gt; to archive a conversation (&lt;kbd&gt;[&lt;/kbd&gt;). Similarly, search can be triggered via &lt;kbd&gt;shift&lt;/kbd&gt;+&lt;kbd&gt;7&lt;/kbd&gt; (&lt;kbd&gt;/&lt;/kbd&gt;). Note that this is &lt;strong&gt;the exact same shortcut that does not work in all the other Google apps&lt;/strong&gt;. So, if you work at Google, maybe ask your colleagues on the Gmail team for a little help.&lt;/p&gt;
&lt;h2 id=&#34;discourse&#34;&gt;Discourse&lt;/h2&gt;
&lt;p&gt;Discourse is a great example because it is an open-source application, meaning that you can go to their &lt;a href=&#34;https://github.com/discourse/discourse&#34;&gt;GitHub repository&lt;/a&gt; and see &lt;a href=&#34;https://github.com/discourse/discourse/blob/f0b2e77abb932f118878951fa75911db2d3a3013/app/assets/javascripts/discourse/app/lib/keyboard-shortcuts.js&#34;&gt;how they are doing it&lt;/a&gt;. The answer is very simple: They use the Mousetrap library with &lt;code&gt;keypress&lt;/code&gt;. As explained above, &lt;code&gt;keyCode&lt;/code&gt;/&lt;code&gt;which&lt;/code&gt;/&lt;code&gt;charCode&lt;/code&gt; will contain the right code even on international layouts when used with &lt;code&gt;keypress&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;linear&#34;&gt;Linear&lt;/h2&gt;
&lt;p&gt;Linear is a relatively new and increasingly popular issue tracking software. It takes pride in having a &lt;em&gt;keyboard first design&lt;/em&gt;, and being &lt;em&gt;optimized for efficiency with extensive keyboard shortcuts&lt;/em&gt;. Their recent success story and my personal experience suggest that they deliver on this promise. Of course, many other web apps have a lot of keyboard shortcuts, too. However, with Linear, they actually work.&lt;/p&gt;

&lt;div class=&#34;notices question&#34;&gt;
    &lt;p&gt;Do you know other applications that work with all keyboard layouts? Please let me know in the comments below, and I will add them here.&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Next time you use an app that dangles its keyboard shortcut definitions in front of you, just so you can find out that they don&amp;rsquo;t work with your layout, please remember this post. You will know that there are ways to fix this.&lt;/p&gt;
&lt;p&gt;If you are in the mood, you can write a nice message to the developers, explaining how they are probably misusing the &lt;code&gt;keydown&lt;/code&gt; event and its &lt;code&gt;keyCode&lt;/code&gt; property. We shouldn&amp;rsquo;t have to live like this.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>An Interactive Virtual Keyboard to Visualize any Collection of Shortcuts</title>
      <link>https://tkainrad.dev/posts/visualize-collections-of-keyboard-shortcuts/</link>
      <pubDate>Fri, 22 Jan 2021 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/visualize-collections-of-keyboard-shortcuts/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;An important part of &lt;a href=&#34;https://keycombiner.com&#34;&gt;KeyCombiner&lt;/a&gt; is displaying collections of keyboard shortcuts. Therefore, I have invested a lot of time to design searching and filtering features that help to browse even large collections.&lt;/p&gt;
&lt;p&gt;Unfortunately, these features are not sufficient when you want to understand a collection of hundreds of shortcuts at a glance. I have been thinking about this problem since I started working on KeyCombiner almost precisely one year ago. Today, I am happy to announce that KeyCombiner offers a solution:&lt;br&gt;
The Shortcut Collection Visualizer&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/collection-visualizer/visual-keyboard-short-blog-bg.gif&#34;
         alt=&#34;Collection Visualizer for XCode, one of very few applications that use all 4 modifier keys at the same time.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Collection Visualizer for &lt;a href=&#34;https://keycombiner.com/collections/xcode/&#34;&gt;XCode&lt;/a&gt;, one of very few applications that use all 4 modifier keys at the same time.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It is heavily inspired by Waldo Bronchart&amp;rsquo;s open-source &lt;a href=&#34;https://github.com/waldobronchart/ShortcutMapper&#34;&gt;Application Shortcut Mapper&lt;/a&gt;. However, it is a new VueJS-based implementation, adding several additional features that work together with the rest of KeyCombiner. Most importantly, it can efficiently process KeyCombiner&amp;rsquo;s collection tables and hence works for any shortcut collection on KeyCombiner, &lt;a href=&#34;https://keycombiner.com/collecting/collections/public/search/?description=dialog&amp;amp;keys=&amp;amp;mac_keys=&amp;amp;submit=Search&#34;&gt;even search results&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to play around with it right away, go to any public KeyCombiner collection, e.g. for &lt;a href=&#34;https://keycombiner.com/collections/vscode/&#34;&gt;VSCode&lt;/a&gt;, &lt;a href=&#34;https://keycombiner.com/collections/intellij-idea/winlinux/&#34;&gt;IntelliJ IDEA&lt;/a&gt;, &lt;a href=&#34;https://keycombiner.com/collections/xcode/&#34;&gt;XCode&lt;/a&gt;, &lt;a href=&#34;https://keycombiner.com/collections/chrome/winlinux/&#34;&gt;Chrome&lt;/a&gt; or one of &lt;a href=&#34;https://keycombiner.com/collections/&#34;&gt;the other 60+ public collections&lt;/a&gt;. If you want to fully understand its potential, please read on.&lt;/p&gt;
&lt;h1 id=&#34;features&#34;&gt;Features&lt;/h1&gt;
&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;/h2&gt;
&lt;p&gt;The virtual keyboard packs a lot of data into a relatively small space. Each button of the keyboard consists of the following elements:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/collection-visualizer/visual-keyboard-description.png&#34;
         alt=&#34;Elements on each key of the virtual keyboard.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Elements on each key of the virtual keyboard.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;grouping-by-modifier-combination&#34;&gt;Grouping by Modifier Combination&lt;/h2&gt;
&lt;p&gt;A proper keyboard shortcut consists of 0 or more modifier keys and exactly one non-modifier key. There are 4 modifier keys:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;Shift&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;Alt&lt;/kbd&gt;&lt;/li&gt;
&lt;li&gt;&lt;kbd&gt;Cmd&lt;/kbd&gt; (macOS) / &lt;kbd&gt;Super&lt;/kbd&gt; (Windows and Linux)&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;This order of modifiers is not random. KeyCombiner &lt;em&gt;always&lt;/em&gt; shows keyboard shortcuts with precisely this order. &lt;a href=&#34;https://twitter.com/ThomasKainrad/status/1340769935971282946&#34;&gt;There are good reasons for this&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;This means that we have four boolean variables, resulting in $2^4$ possible modifier combinations. For each of these 16 states, the Collection Visualizer uses a different background color, or background gradient if there are multiple active modifiers.&lt;/p&gt;
&lt;p&gt;To toggle modifiers, click on the virtual buttons with your mouse, or press the respective modifier key on your physical keyboard. The entire virtual keyboard will then update according to the active combination of modifiers.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/collection-visualizer/all-modifier-states.gif&#34;
         alt=&#34;It is very rare that a key has a shortcut for every modifier combination. However, it can happen, especially when combining shortcuts of multiple applications in personal collections. (Please don&amp;amp;rsquo;t tell me I forgot one of the 16 modifier combinations - it took me way too long to create this animation.)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;It is very rare that a key has a shortcut for every modifier combination. However, it can happen, especially when combining shortcuts of multiple applications in personal collections. &lt;br&gt; (Please don&amp;rsquo;t tell me I forgot one of the 16 modifier combinations - it took me way too long to create this animation.)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;filter-collection-table&#34;&gt;Filter Collection Table&lt;/h2&gt;
&lt;p&gt;One of my favorite things about KeyCombiner&amp;rsquo;s shortcut collections is that I can filter them by context, category, or modifier combination with a single click using the panes on the side.&lt;/p&gt;
&lt;p&gt;The collection visualizer expands on this concept. If you click on any non-modifier key, the collection table will show all shortcuts that use this particular key. To show all shortcuts containing the key &lt;kbd&gt;F&lt;/kbd&gt; click on the F button on the virtual keyboard.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/collection-visualizer/filter-by-key-press.gif&#34;
         alt=&#34;Filtering the collection table for all shortcuts that contain the F key.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Filtering the collection table for all shortcuts that contain the &lt;kbd&gt;F&lt;/kbd&gt; key.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;real-time-updates-on-changes&#34;&gt;Real-time updates on changes&lt;/h2&gt;
&lt;p&gt;Building personal collections of keyboard shortcuts and text snippets is the foundational concept behind KeyCombiner. You can then practice these collections with its interactive trainer, relying on spaced repetition techniques and advanced statistics to guide your learning progress. You can also use &lt;a href=&#34;https://keycombiner.com/desktop/&#34;&gt;KeyCombiner Desktop&lt;/a&gt; to instantly look up all combinations in your collections without leaving your current context.&lt;/p&gt;
&lt;p&gt;Oh wait, I am getting side-tracked. I meant to say that the collection visualizer updates immediately whenever you make a change to one of your collections. A change could be adding new shortcuts, editing existing entries, or re(moving) entries. This works in the blink of an eye, even if you remove hundreds of combinations at once.&lt;/p&gt;
&lt;h2 id=&#34;additional-features&#34;&gt;Additional Features&lt;/h2&gt;
&lt;p&gt;I am getting the sense that this post will be too long for the average person&amp;rsquo;s interest in keyboard shortcuts. So, I will list some additional features in shorter form:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There are three levels of opacity:
&lt;ol&gt;
&lt;li&gt;Keys without any mapped combinations&lt;/li&gt;
&lt;li&gt;Keys with mapped shortcuts, but none that use the current modifiers&lt;/li&gt;
&lt;li&gt;Keys that have a combination with the currently activated modifiers&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/collection-visualizer/opacity.png&#34;
         alt=&#34;Different levels of opacity carry information.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Different levels of opacity carry information.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;If, for any modifier combination, there are two or more shortcuts bound to a key, the number of combinations in the top right of the button is marked red.&lt;/li&gt;
&lt;li&gt;If there are two or more combinations on a key for the current modifier state, the shortcut description for this key says &lt;em&gt;Conflict&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;There is a small text below the virtual keyboard saying how many shortcuts are mapped onto the virtual keyboard, and how many combinations had to be skipped. (See &lt;a href=&#34;#current-limitations&#34;&gt;Current Limitations&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;The keyboard must be in focus if you want to activate modifiers by pressing the respective buttons on your physical keyboard. This is so that you can still use &lt;kbd&gt;Ctrl&lt;/kbd&gt; and &lt;kbd&gt;Shift&lt;/kbd&gt; for table selection operations without affecting the visualizer. Buttons below the virtual keyboard allow toggling the focus.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;use-cases&#34;&gt;Use Cases&lt;/h1&gt;
&lt;h2 id=&#34;quickly-grasp-a-set-of-shortcuts&#34;&gt;Quickly Grasp a Set of Shortcuts&lt;/h2&gt;
&lt;p&gt;Perhaps the most obvious use case is exploring a collection of shortcuts. The visual keyboard helps immensely in this process. Within seconds, you can get a feeling of which modifiers are used by a specific application and whether it uses Vim-like home row navigation or something else entirely.&lt;/p&gt;
&lt;p&gt;The different layers of opacity aid this use case. Without activating any modifiers, you can already understand where the most shortcuts are located. This is supported further by the combination count in each virtual keyboard button&amp;rsquo;s top right.
Filtering the collection table by clicking on a specific key lets you see all shortcuts for that key and understand how they are related.&lt;/p&gt;
&lt;h2 id=&#34;see-conflicts-and-free-combinations&#34;&gt;See Conflicts and Free Combinations&lt;/h2&gt;
&lt;p&gt;At the moment, this is my favorite use case, as I have used it plenty of times already with great success.&lt;/p&gt;
&lt;p&gt;I recently &lt;a href=&#34;https://tkainrad.dev/posts/learning-all-vscode-shortcuts-evolved-my-developing-habits/&#34;&gt;learned all VSCode shortcuts&lt;/a&gt; with KeyCombiner&amp;rsquo;s interactive trainer. However, since then, I have started to experiment with &lt;a href=&#34;https://tkainrad.dev/posts/learning-all-vscode-shortcuts-evolved-my-developing-habits/&#34;&gt;Foam&lt;/a&gt; and picked up some other extensions. All of these come with their own set of commands. So, I frequently have to find an available key combination for a new command I want to use efficiently. VSCode itself is not much help with that. It tells you &lt;em&gt;after&lt;/em&gt; setting a combination that it is already taken:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/collection-visualizer/vscode-binding-exists.png&#34;
         alt=&#34;Different levels of opacity carry information.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Different levels of opacity carry information.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I guess it&amp;rsquo;s better than nothing, but trying multiple combinations and manually checking what other combination is already using that binding and whether you might be able to remove that other binding is not much fun.
The collection visualizer made it trivial to see that there are actually plenty of free combinations in VSCode, only &lt;kbd&gt;Ctrl&lt;/kbd&gt; and &lt;kbd&gt;Shift&lt;/kbd&gt; are quite busy by default. Things start happening if you mix in &lt;kbd&gt;Alt&lt;/kbd&gt;:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/collection-visualizer/vscode-free-combinations.png&#34;
         alt=&#34;All modifier combinations with Alt are wide open for your own assignments in VSCode.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;All modifier combinations with &lt;kbd&gt;Alt&lt;/kbd&gt; are wide open for your own assignments in VSCode.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;You can then go one step further and find free combinations that are easy to type. For me, these are combinations that I can type with just my left hand. If the non-modifier key is on the home row, that&amp;rsquo;s another big plus. In any case, a convenient shortcut should have a maximum of two modifiers.&lt;/p&gt;
&lt;h2 id=&#34;design-a-coherent-set-of-shortcuts&#34;&gt;Design a Coherent Set of Shortcuts&lt;/h2&gt;
&lt;p&gt;The collection visualizer helps design a coherent set of shortcuts, either for yourself or for an application you are developing.&lt;/p&gt;
&lt;p&gt;Unfortunately, many application designers do not think very hard about keyboard shortcuts. Often, you end up with a set that is neither intuitive nor easy to type. Heck, even &lt;a href=&#34;https://keycombiner.com/collections/keycombiner/&#34;&gt;KeyCombiner&amp;rsquo;s own shortcuts&lt;/a&gt; are all over the place with sequences and different modifier combinations. Given that I work more or less alone on the project and try to be very efficient with my time, I didn&amp;rsquo;t think about these bindings enough. The collection visualizer makes this painfully obvious, and I will soon come up with new shortcuts. However, it will be very hard not to annoy users who have already memorized these shortcuts.
So, I recommend that you be smarter than me and start to design a coherent set of shortcuts for your application right away. The collection visualizer is here to help you with that.&lt;/p&gt;
&lt;p&gt;If you are not an application designer, you might still want to design a coherent set of key bindings for your personal use. Without any tools to assist you, this is a surprisingly hard tasks, especially when you try to find a coherent set for or &lt;em&gt;multiple&lt;/em&gt; applications. You have to keep in mind which commands are available in these different apps, what the defaults are, and how to resolve these constraints into a set that works everywhere.
The collection visualizer, along with KeyCombiner&amp;rsquo;s other collection management features, can help you get there.&lt;/p&gt;
&lt;h1 id=&#34;current-limitations&#34;&gt;Current Limitations&lt;/h1&gt;
&lt;p&gt;Above, I have written that a proper keyboard shortcut consists of 0 or more modifier keys and exactly one non-modifier key.  However, KeyCombiner also allows sequences, such as the &lt;em&gt;Go To&lt;/em&gt; shortcuts used by Gmail. I have been thinking a lot about how to visualize those on a virtual keyboard, but have not found a good solution yet.&lt;/p&gt;
&lt;p&gt;Furthermore, KeyCombiner collections can also hold short text snippets, such as commands and programming language syntax. Many people use these snippets with the &lt;a href=&#34;https://tkainrad.dev/posts/app-to-show-shortcuts-of-current-application-windows-linux-macos&#34;&gt;Desktop Apps&#39; instant lookup&lt;/a&gt;. It turns your collections into an instant, context-aware, searchable cheatsheet. However, I struggle to find a way to visualize them on a keyboard.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;In its first days, the collection visualizer has already helped me plenty of times. I improved my VSCode bindings, realized that KeyCombiner&amp;rsquo;s own default bindings are not intuitive, and found better ways to reuse my VSCode bindings in PyCharm and Eclipse.
I&amp;rsquo;d be thrilled to hear about your experiences in the comments below or via &lt;a href=&#34;mailto:thomas@tkainrad.dev&#34;&gt;mail&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;I will write about the collection visualizer&amp;rsquo;s implementation in a future blog post. Spoiler: Vue and (S)CSS do the heavy lifting.&lt;/p&gt;

&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>An app to show the shortcuts of the current application for Windows, Linux, and macOS</title>
      <link>https://tkainrad.dev/posts/app-to-show-shortcuts-of-current-application-windows-linux-macos/</link>
      <pubDate>Sat, 12 Dec 2020 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/app-to-show-shortcuts-of-current-application-windows-linux-macos/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Looking up keyboard shortcuts on the web takes you out of the current context and breaks your workflow. That&amp;rsquo;s why, &lt;a href=&#34;https://tkainrad.dev/posts/setting-up-linux-workstation/&#34;&gt;as a Linux user&lt;/a&gt;, I have always been a bit envious of macOS users, who had access to tools that could instantly show the current application&amp;rsquo;s shortcuts, such as &lt;a href=&#34;https://www.mediaatelier.com/CheatSheet/&#34;&gt;CheatSheet&lt;/a&gt;, and &lt;a href=&#34;https://github.com/amiechen/pretzel&#34;&gt;Pretzel&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On Windows and Linux, there was no such thing. You had to find shortcut information in the software&amp;rsquo;s documentation and hope that it was searchable. Or even worse, suffer a massive context switch and google for the shortcuts, until now. KeyCombiner Desktop is free to use and can show the active application&amp;rsquo;s shortcuts on Windows, Linux, and macOS.&lt;/p&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;Suppose you are on macOS and already use one of the existing solutions. In that case, you might still want to read further because KeyCombiner goes far beyond looking up the active application&amp;rsquo;s shortcuts.&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;how-does-it-look-like&#34;&gt;How does it look like?&lt;/h1&gt;
&lt;p&gt;If a picture says more than 1000 words, an animation will speak for itself:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/lookup-three-apps2.gif&#34;
         alt=&#34;Instant shortcut lookup for three example applications.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Instant shortcut lookup for three example applications.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Upon pressing the global trigger, the lookup will appear with a search field in focus, where you can start to type right away. The default trigger is &lt;kbd&gt;super&lt;/kbd&gt;+&lt;kbd&gt;shift&lt;/kbd&gt;+&lt;kbd&gt;c&lt;/kbd&gt; on Windows and Linux, and &lt;kbd&gt;cmd&lt;/kbd&gt;+&lt;kbd&gt;shift&lt;/kbd&gt;+&lt;kbd&gt;k&lt;/kbd&gt; on macOS. This can be configured, I use &lt;kbd&gt;super&lt;/kbd&gt;+&lt;kbd&gt;c&lt;/kbd&gt; on Linux.&lt;/p&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;The search terms will be applied to the shortcut description, the key binding, and some hidden fields like the shortcut category. Do you want to search for all file-related bindings that use &lt;kbd&gt;Ctrl&lt;/kbd&gt;? Type something like &lt;code&gt;file ctrl&lt;/code&gt; and you have your answer.&lt;/p&gt;

&lt;/div&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;The lookup window disappears whenever you click outside of it, when you press &lt;kbd&gt;esc&lt;/kbd&gt;, or when you switch to another application via &lt;kbd&gt;cmd&lt;/kbd&gt;/&lt;kbd&gt;ctrl&lt;/kbd&gt;+&lt;kbd&gt;tab&lt;/kbd&gt;. If you close via &lt;kbd&gt;esc&lt;/kbd&gt;, the search term will be cleared. If you use &lt;kbd&gt;cmd&lt;/kbd&gt;/&lt;kbd&gt;ctrl&lt;/kbd&gt;+&lt;kbd&gt;tab&lt;/kbd&gt;, you can switch back to the active application without losing the cursor position.&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;what-else-can-it-do&#34;&gt;What else can it do?&lt;/h1&gt;
&lt;p&gt;If you carefully watched the animation above, you might have noticed that the rows are grouped. The first group has the prefix &lt;code&gt;Active:&lt;/code&gt; and is highlighted with a blue background. This group shows the shortcuts for the active application. Everything that is shown below this group is from the user&amp;rsquo;s personal shortcut and command collections.&lt;/p&gt;
&lt;p&gt;To explain what these are, I have to provide some background. KeyCombiner is an application to organize, learn, and practice keyboard shortcuts. A core idea is to learn precisely the shortcuts you need. The only way to achieve this is to choose them yourself, which is done by creating personal collections of shortcuts and text snippets. I like to compare this concept to how you build playlists in music software. Instead of browsing your favorite artists’ albums, KeyCombiner allows you to browse shortcut collections of your favorite applications. Instead of adding songs to your playlists, you can add keyboard shortcuts and text snippets to your personal collections.&lt;/p&gt;
&lt;p&gt;The instant lookup of KeyCombiner Desktop profits immensely from these collection building features, and vice-versa. Having instant access to your collections makes them a lot more valuable. In short, these two features complete each other.&lt;/p&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;The instant lookup works offline. Your personal collections are downloaded only on application startup. If you modified your collections while KeyCombiner Desktop was running, you may reload the lookup via (&lt;kbd&gt;Ctrl&lt;/kbd&gt;/&lt;kbd&gt;Cmd&lt;/kbd&gt;+&lt;kbd&gt;R&lt;/kbd&gt;).&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;I will try to illustrate this with my personal experience. Currently, I have 10 personal KeyCombiner collections. My largest one contains more or less all shortcuts that I already knew when I started to use KeyCombiner. This collection is publicly accessible via this &lt;a href=&#34;https://keycombiner.com/collecting/collections/shared/89e47af4-0f2e-4d5d-98fa-6966bc7453cc/&#34;&gt;shareable link&lt;/a&gt;. Then, I did a &lt;a href=&#34;https://tkainrad.dev/posts/how-i-learned-50-new-keyboard-shortcuts-in-42-minutes/&#34;&gt;blog post challenge to learn 50 new keyboard shortcuts as fast as possible&lt;/a&gt;. More recently, I &lt;a href=&#34;https://tkainrad.dev/posts/learning-all-vscode-shortcuts-evolved-my-developing-habits/&#34;&gt;learned &lt;em&gt;all&lt;/em&gt; VSCode keyboard shortcuts&lt;/a&gt;, which evolved my developing habits and added another collection with 151 shortcuts to my repertoire.
Add to that a few smaller collections I used for learning the shortcuts of Chrome DevTools, Nautilus, Spotify, and Vimium. Having instant access to all of these shortcuts without leaving my current context feels like a superpower, even though I admit that some of those superpowers you see in movies look even more powerful.&lt;/p&gt;
&lt;p&gt;My most recent new use case is to look up regular expressions. There is an &lt;a href=&#34;https://tkainrad.dev/posts/automatically-add-kbd-tags-with-a-single-regex/&#34;&gt;article&lt;/a&gt; on how to automatically add HTML &amp;lt;kbd&amp;gt;-tags to any text via a single regex replace operation. I use this for all of my blog posts, all &amp;lt;kbd&amp;gt; tags you see in this post are added this way. However, it wouldn&amp;rsquo;t be so convenient if I couldn&amp;rsquo;t instantly look up and copy-paste the required regular expression:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/lookup-regex.gif&#34;
         alt=&#34;The lookup also shows shortcuts and text snippets from your personal collections.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The lookup also shows shortcuts and text snippets from your personal collections.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;KeyCombiner&amp;rsquo;s instant lookup is a cheatsheet of sorts. But it is context-aware, searchable, and you can expand it quickly by importing from public or shared collections. It is also friendlier to the environment than printing out PDFs and hanging them next to your desk.&lt;/p&gt;
&lt;h1 id=&#34;how-does-it-know-the-active-applications-shortcuts&#34;&gt;How does it know the active application&amp;rsquo;s shortcuts?&lt;/h1&gt;
&lt;p&gt;There is a very boring answer to this question: By maintaining an extensive public database of keyboard shortcuts. You might have noticed from reading this post that I am quite excited about Keycombiner. That&amp;rsquo;s why I spend considerable time to parse the documentation pages of software tools for their keyboard shortcuts. It&amp;rsquo;s not as dull as it sounds like; creative use of regular expressions makes it quite fun. Don&amp;rsquo;t judge me; everyone has a weird hobby.&lt;/p&gt;
&lt;p&gt;You can browse the database at &lt;a href=&#34;https://keycombiner.com/collections/&#34;&gt;https://keycombiner.com/collections/&lt;/a&gt;.&lt;br&gt;
The instant lookup works for all desktop-based applications that are listed there.  In particular, it should work for all apps that an average software developer uses (VSCode, JetBrains IDEs, Vim, Eclipse, Chrome, Firefox, Safari, Nautilus, Finder, Notion, Slack, Explorer, Terminal, iTerm2, Obsidian,&amp;hellip;). This list is growing fast, and I am happy to take suggestions.&lt;/p&gt;
&lt;p&gt;One downside of this approach is that KeyCombiner can only show the default bindings for the active application. If you have changed these bindings, that&amp;rsquo;s not ideal. Fortunately, If you went through the trouble of changing a binding, you usually won&amp;rsquo;t need to look it up. In any case, you can add the customized binding to a personal collection, and the lookup will show it as explained above.&lt;/p&gt;
&lt;p&gt;Existing tools for macOS use a different approach. They rely on a macOS API to retrieve menu keyboard shortcuts for the current application. This has its advantages and disadvantages. The great thing about it is that it works automatically for all applications that have a menu. On the other hand, applications
don&amp;rsquo;t necessarily register all of their shortcuts as menu shortcuts.&lt;/p&gt;
&lt;!-- Currently, these application are supported:
* KeyCombiner Desktop
* Google Chrome
* Firefox
* Safari
* Visual Studio Code
* Eclipse IDE
* JetBrains IntelliJ Idea
* JetBrains CLion
* JetBrains PhpStorm
* JetBrains PyCharm
* JetBrains GoLand
* JetBrains RubyMine
* JetBrains MPS
* Android Studio
* Gnome Terminal
* Tilix
* iTerm2
* VLC Media Player
* Spotify
* Notion
* Airtable
* Slack
* GVim
* Obsidian(.md)
* Windows File Explorer
* Gnome Files (Nautilus)
* Finder (macOS File Explorer) --&gt;
&lt;!-- This list will grow rapidly in the future. Have an application that you need urgently? [Contact me](mailto:thomas@tkainrad.dev) and I will see what I can do. --&gt;
&lt;h1 id=&#34;how-can-i-use-it&#34;&gt;How can I use it?&lt;/h1&gt;
&lt;p&gt;Simply go to &lt;a href=&#34;https://keycombiner.com/desktop/&#34;&gt;https://keycombiner.com/desktop/&lt;/a&gt; and grab the installer for your system. On macOS, you need to grant some permissions for the instant lookup to work. Please refer to the bottom of the linked page for instructions on how to do that.&lt;/p&gt;
&lt;p&gt;KeyCombiner is a SaaS with a generous free tier. &lt;strong&gt;All features described in this article are entirely free to use.&lt;/strong&gt; If you use KeyCombiner a lot, please consider upgrading to a Pro subscription to get access to additional features and to support hosting and development efforts.&lt;/p&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;Are you a developer? You might be interested in how I implemented the checkout process for the Pro subscription. I recently &lt;a href=&#34;https://tkainrad.dev/posts/implementing-paddle-payments-for-my-django-saas/&#34;&gt;published a blog post describing the implementation&lt;/a&gt; in detail.&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;KeyCombiner can do a lot more things than what&amp;rsquo;s described in this post. However, the instant lookup has quickly become one of my favorite features. Admittedly, I don&amp;rsquo;t use KeyCombiner every day for learning shortcuts. Usually, after I have &lt;a href=&#34;https://tkainrad.dev/posts/learning-all-vscode-shortcuts-evolved-my-developing-habits/&#34;&gt;mastered all shortcuts&lt;/a&gt; of a new collection, it takes a couple of days or even weeks until I get back to frequent practice.&lt;/p&gt;
&lt;p&gt;In contrast, I use the instant lookup &lt;em&gt;all the time&lt;/em&gt;. If I am on a different computer without KeyCombiner installed, I notice that something is missing after a couple of minutes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Enough about me, though. I&amp;rsquo;d be thrilled to hear about your experience with the software.&lt;/strong&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Automatically add &lt;kbd&gt;-tags with a Single Regex</title>
      <link>https://tkainrad.dev/posts/automatically-add-kbd-tags-with-a-single-regex/</link>
      <pubDate>Fri, 04 Dec 2020 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/automatically-add-kbd-tags-with-a-single-regex/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Until about a year ago, I had the same approach to regular expressions as most developers: Doing trial and error until the damn thing was working.&lt;/p&gt;
&lt;p&gt;Then came &lt;a href=&#34;https://keycombiner.com&#34;&gt;KeyCombiner&lt;/a&gt; and with it some very specific challenges around parsing text that contains keyboard shortcuts. One of these challenges is writing blog posts about KeyCombiner:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://tkainrad.dev/posts/a-collection-of-all-keyboard-shortcuts-i-use/&#34;&gt;A Collection of all Keyboard Shortcuts I use&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://tkainrad.dev/posts/learning-all-vscode-shortcuts-evolved-my-developing-habits/&#34;&gt;Learning all VSCode shortcuts evolved my developing habits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://tkainrad.dev/posts/how-i-learned-50-new-keyboard-shortcuts-in-42-minutes/&#34;&gt;How I learned 50 new keyboard shortcuts in 42 minutes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These posts contain &lt;em&gt;a lot&lt;/em&gt; of keyboard shortcuts that should be highlighted via &amp;lt;kbd&amp;gt; tags. By now, these tags are supported by most static site generators and blogging platforms, such as DEV.to. However, typing them in manually is extremely tedious. Initially, I felt quite smart when I added a user snippet in VSCode:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;kbd&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;:&lt;/span&gt; {
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;scope&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;markdown&amp;#34;&lt;/span&gt;,
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;prefix&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;kbd&amp;#34;&lt;/span&gt;,
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;body&amp;#34;&lt;/span&gt;: [
        &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&amp;lt;kbd&amp;gt;$0&amp;lt;/kbd&amp;gt;&amp;#34;&lt;/span&gt;
    ],
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;description&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;Insert HTML kbd tags&amp;#34;&lt;/span&gt;
}&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This lets me type &lt;code&gt;kbd&lt;/code&gt;, press &lt;kbd&gt;ctrl&lt;/kbd&gt;+&lt;kbd&gt;space&lt;/kbd&gt; and hit &lt;kbd&gt;enter&lt;/kbd&gt; to put my cursor in-between an opening and a closing &lt;code&gt;&amp;lt;kbd&amp;gt;&lt;/code&gt; tag. However, it does not help with copy-pasted text, which is an essential use case for me. I write the first draft of my posts in Notion, as described in &lt;a href=&#34;https://tkainrad.dev/posts/managing-my-personal-knowledge-base/&#34;&gt;my post about personal knowledge management&lt;/a&gt;. Even if this were not an issue, it&amp;rsquo;s still quite a lot of keys to hit for just adding two tags. Another solution would be to write a script in some interpreted language and do all kinds of fancy things to this text. The problem is, this script will likely end up like many single-purpose scripts do: Abandoned and buggy.&lt;/p&gt;
&lt;p&gt;So, I needed a better, more timeless solution: Regular Expressions.&lt;br&gt;
Fear not, I won&amp;rsquo;t try to teach you general regex syntax. Instead, I will show how to use it for this particular use case.
I will explain in detail how and why this works, but you can also just copy-paste &lt;a href=&#34;#final-regex&#34;&gt;the final regex&lt;/a&gt; and apply it for yourself. One of the best tools for putting the regex to work is Visual Studio Code, but any editor that supports regex-based replace operations will do.&lt;/p&gt;
&lt;h1 id=&#34;how-to&#34;&gt;How-To&lt;/h1&gt;
&lt;p&gt;Suppose we have the following text:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Modifier keys, such as ctrl, shift, alt, and cmd do nothing by themselves but can be used in combination with other keys. To paste without the original formatting, use ctrl+shift+v. Debugging is controlled via f5, f6, f10, and f11.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What we want to have is this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Modifier keys, such as &lt;kbd&gt;ctrl&lt;/kbd&gt;, &lt;kbd&gt;shift&lt;/kbd&gt;, &lt;kbd&gt;alt&lt;/kbd&gt;, and &lt;kbd&gt;cmd&lt;/kbd&gt;  do nothing by themselves but can be used in combination with other keys. To paste without the original formatting, use &lt;kbd&gt;ctrl&lt;/kbd&gt;+&lt;kbd&gt;shift&lt;/kbd&gt;+&lt;kbd&gt;v&lt;/kbd&gt;. Debugging is controlled via &lt;kbd&gt;f5&lt;/kbd&gt;, &lt;kbd&gt;f6&lt;/kbd&gt;, &lt;kbd&gt;f10&lt;/kbd&gt;, and &lt;kbd&gt;f11&lt;/kbd&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The next sections will describe how we get there.&lt;/p&gt;
&lt;h2 id=&#34;modifiers-and-f-keys&#34;&gt;Modifiers and F-keys&lt;/h2&gt;
&lt;p&gt;We can already go pretty far with this regex that uses or operators (&lt;code&gt;|&lt;/code&gt;) to match a predefined set of strings:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;(f1|f2|f3|f4|f5|f6|f7|f8|f9|f10|f11|f12|backspace|tab|enter|shift|ctrl|cmd|alt|capslock|pageup|pagedown|ins|del|option|meta)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Putting the expression in brackets &lt;code&gt;()&lt;/code&gt; means that it is a capture group. Capture groups are an essential concept when working with regular expressions. One of their greatest features is that we get to reference them by number in the replacement string:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;kbd&lt;/span&gt;&amp;gt;$1&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;kbd&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This means we will replace a match of the first capture group with itself (&lt;code&gt;$1&lt;/code&gt;) surround by the desired tags.&lt;/p&gt;
&lt;p&gt;The above text will become:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Modifier keys, such as &lt;kbd&gt;ctrl&lt;/kbd&gt;, &lt;kbd&gt;shift&lt;/kbd&gt;, &lt;kbd&gt;alt&lt;/kbd&gt;, and &lt;kbd&gt;cmd&lt;/kbd&gt;  do nothing by themselves but can be used in combination with other keys. To paste without the original formatting, use &lt;kbd&gt;ctrl&lt;/kbd&gt;+&lt;kbd&gt;shift&lt;/kbd&gt;+v. Debugging is controlled via &lt;kbd&gt;f5&lt;/kbd&gt;, &lt;kbd&gt;f6&lt;/kbd&gt;, &lt;kbd&gt;f10&lt;/kbd&gt;, and &lt;kbd&gt;f11&lt;/kbd&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is a good start, but we are not quite there yet. Non-modifier keys that are part of key combinations do not yet have the proper tags. We will deal with them in the next section. But before, we can shorten our expression a bit, by switching out the f-keys with a single sub-expression: &lt;code&gt;f([2-9]|1[0-2]?)&lt;/code&gt;. This will match any &lt;code&gt;f&lt;/code&gt;-character followed by either &lt;code&gt;2-9&lt;/code&gt; or a sequence of &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;0-2&lt;/code&gt;. It is equal to typing out the f-keys, but a little more fun. So we end up with this expression:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;(f([2-9]|1[0-2]?)|backspace|tab|enter|shift|ctrl|cmd|alt|capslock|pageup|pagedown|ins|del|option|meta)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;regular-keys-in-combinations&#34;&gt;Regular keys in combinations&lt;/h2&gt;
&lt;p&gt;Matching regular keys is a bit more tricky. We cannot just match any &lt;code&gt;v&lt;/code&gt; characters in our text. One approach is to match any single alphanumeric character that is preceded by a &lt;code&gt;+&lt;/code&gt;-sign. To do this, we need to use something called &lt;em&gt;Lookarounds&lt;/em&gt;, more more specifically, a &lt;em&gt;Lookbehind&lt;/em&gt;: &lt;code&gt;(?&amp;lt;=\+)&lt;/code&gt;
The nice thing about lookarounds is that they do not increase the matched character sequence. Instead, they specify what must (or must not) come before or after the matched part, which is very useful when applying replacement operations. Adding this to our expression results in the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;((?&amp;lt;=\+)[\w\d]{1}|f([2-9]|1[0-2]?)|backspace|tab|enter|shift|ctrl|cmd|alt|capslock|pageup|pagedown|ins|del|option|meta)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Using this expression, we end up with the desired text already:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Modifier keys, such as &lt;kbd&gt;ctrl&lt;/kbd&gt;, &lt;kbd&gt;shift&lt;/kbd&gt;, &lt;kbd&gt;alt&lt;/kbd&gt;, and &lt;kbd&gt;cmd&lt;/kbd&gt;  do nothing by themselves but can be used in combination with other keys. To paste without the original formatting, use &lt;kbd&gt;ctrl&lt;/kbd&gt;+&lt;kbd&gt;shift&lt;/kbd&gt;+&lt;kbd&gt;v&lt;/kbd&gt;. Debugging is controlled via &lt;kbd&gt;f5&lt;/kbd&gt;, &lt;kbd&gt;f6&lt;/kbd&gt;, &lt;kbd&gt;f10&lt;/kbd&gt;, and &lt;kbd&gt;f11&lt;/kbd&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;However, we shouldn&amp;rsquo;t celebrate too early. There are still some issues and special cases to take care of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What happens if a modifier appears within another word, such as &lt;em&gt;ins&lt;/em&gt; in &lt;em&gt;&lt;strong&gt;ins&lt;/strong&gt;ert&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;What happens if we apply this to text that already has some &lt;code&gt;&amp;lt;kbd&amp;gt;&lt;/code&gt; tags?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The answer to both questions is clear: Terrible things will happen. We will have to go a bit deeper.&lt;/p&gt;
&lt;h2 id=&#34;avoiding-false-positives&#34;&gt;Avoiding False Positives&lt;/h2&gt;
&lt;p&gt;Now that we already know the &lt;em&gt;Lookaround&lt;/em&gt; concept, this is relatively easy. We will ensure that the expression only matches occurrences surrounded by one of these characters: &lt;code&gt;[\+ .,\n]&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;(?&amp;lt;=[\+ .,\n])(backspace|tab|enter|shift|ctrl|cmd|alt|capslock|pageup|pagedown|ins|del|option|meta|f([2-9]|1[0-2]?)|(?&amp;lt;=\+)[\w\d]{1})(?=[\+ .,\n])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;achieving-idempotence&#34;&gt;Achieving Idempotence&lt;/h2&gt;
&lt;p&gt;An idempotent operation is one that can be applied more than once without changing the outcome. To achieve idempotence, we will make use of another advantage of our beloved lookarounds: They can be stacked one after another. All we have to do is make sure that our matches are not already preceded by a &lt;code&gt;&amp;lt;kbd&amp;gt;&lt;/code&gt; tag, using a negative &lt;em&gt;Lookbehind&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;(?&amp;lt;!&amp;lt;kbd&amp;gt;)(?&amp;lt;=[\+ .,\n])(backspace|tab|enter|shift|ctrl|cmd|alt|capslock|pageup|pagedown|ins|del|option|meta|f([2-9]|1[0-2]?)|(?&amp;lt;=\+)[\w\d]{1})(?=[\+ .,\n])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We could add a negative &lt;em&gt;Lookahead&lt;/em&gt; for the closing tag, too, but it is not needed.&lt;/p&gt;
&lt;h2 id=&#34;final-regex&#34;&gt;Final regex&lt;/h2&gt;
&lt;p&gt;That&amp;rsquo;s it. We have a regular expression that we can use in VSCode and other editors to automatically surround all occurences of keys with &lt;code&gt;&amp;lt;kbd&amp;gt;&lt;/code&gt; tags. To use it, search for this expression with the &lt;em&gt;Use Regular Expression&lt;/em&gt; (&lt;kbd&gt;alt&lt;/kbd&gt;+&lt;kbd&gt;r&lt;/kbd&gt;)-toggle enabled:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;(?&amp;lt;!&amp;lt;kbd&amp;gt;)(?&amp;lt;=[\+ .,\n])(backspace|tab|enter|shift|ctrl|cmd|alt|capslock|pageup|pagedown|ins|del|option|meta|f([2-9]|1[0-2]?)|(?&amp;lt;=\+)[\w\d]{1})(?=[\+ .,\n])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, replace all occurrences  string:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;kbd&lt;/span&gt;&amp;gt;$1&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;kbd&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you want to go one step further, you could even apply this in a build pipeline, e.g., after pushing to GitHub Pages or GitLab Pages. Personally, I like to do it myself in VSCode. These are very satisfying 30 seconds of work before publishing a new blog post.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Hopefully, I could help a bit to improve the bad reputation of regular expressions. But I will let you in on a little secret. Just like everyone else, I have a hard time remembering the syntax.
The good thing is, I have a superpower to overcome this: KeyCombiner&amp;rsquo;s instant shortcut lookup. Whenever I press &lt;kbd&gt;super&lt;/kbd&gt;+&lt;kbd&gt;c&lt;/kbd&gt;, I have instant access to the shortcuts of the active application, all other shortcuts that I like to use, and even regex syntax:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/lookup-regex.gif&#34;
         alt=&#34;Instant shortcut lookup of KeyCombiner&amp;amp;rsquo;s free to use desktop app.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Instant shortcut lookup of KeyCombiner&amp;rsquo;s free to use &lt;a href=&#34;https://keycombiner.com/desktop/&#34;&gt;desktop app&lt;/a&gt;.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

</description>
    </item>
    
    <item>
      <title>Learning all VSCode shortcuts evolved my developing habits</title>
      <link>https://tkainrad.dev/posts/learning-all-vscode-shortcuts-evolved-my-developing-habits/</link>
      <pubDate>Sun, 15 Nov 2020 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/learning-all-vscode-shortcuts-evolved-my-developing-habits/</guid>
      <description>&lt;h1 id=&#34;preface&#34;&gt;Preface&lt;/h1&gt;
&lt;p&gt;I spent a few hours spread over the last couple of weeks to learn every single one of VSCode&amp;rsquo;s keyboard shortcuts, specifically the 149 shortcuts from the &lt;a href=&#34;https://code.visualstudio.com/shortcuts/keyboard-shortcuts-windows.pdf&#34;&gt;official keyboard shortcuts reference&lt;/a&gt;. These are also present in &lt;a href=&#34;https://keycombiner.com/collections/vscode/&#34;&gt;KeyCombiner&amp;rsquo;s searchable table of VSCode shortcuts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Admittedly, I started this challenge to write a blog post about it afterward and as a way to promote &lt;a href=&#34;https://keycombiner.com/&#34;&gt;KeyCombiner&lt;/a&gt; via content marketing. The blog post I had in mind would be similar to my previous one, &lt;a href=&#34;https://tkainrad.dev/posts/how-i-learned-50-new-keyboard-shortcuts-in-42-minutes/&#34;&gt;documenting my process of learning 50 new web application shortcuts in 42 minutes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But, as it usually happens only in romantic comedies, my intentions changed over time. This article turned out much different. It is not about the learning process but about how this challenge changed some of the developing habits I had for many years. Workflows that I grew used to during many projects, different IDEs, and even varying programming languages suddenly evolved.&lt;/p&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;In case you do want to see how detailed statistics about how KeyCombiner helped me to learn all VSCode shortcuts, have a look at this &lt;a href=&#34;https://keycombiner.com/training/collection/295/&#34;&gt;overview&lt;/a&gt;. It shows a timeline of how my knowledge evolved. It took 205 practice runs (=205 minutes ~ 3.5 hours) until I mastered the complete collection.&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;habits-are-hard-to-break&#34;&gt;Habits are Hard to Break&lt;/h1&gt;
&lt;p&gt;As software engineers, we like to think that we are continually learning and improving our skills. This is certainly true when it comes to the technologies we are using. However, our development habits, such as which features of our IDEs we use, are much more rigid. I believe most of our core workflows are formed early on and very hard to change.
I don&amp;rsquo;t have data to back this up, but when I look at myself, my co-workers, and other developers I know, this certainly seems true. If you think about it, it is not surprising, the same thing happens in other areas of life. For this very reason, you will find that developers who have studied or worked together will often use similar shortcuts, similar IDE features, and have similar limitations in their workflows.&lt;/p&gt;
&lt;p&gt;The usual way to combat this is reading books, blog posts, attending conferences, well, everything that is used in our industry to extend our knowledge. However, with fundamental developing habits, I have found that this works poorly. You might read about a neat feature of your IDE, or see someone demo their workflow, but when you are eventually sitting in front of your screen and writing code, it is easy to fall back to what we already know, what is already set up, and proven to work.&lt;/p&gt;
&lt;p&gt;After this experiment, I am convinced that keyboard shortcuts are the main factor in how we form our developing habits, and they can also stop us from evolving our habits.
Even before this challenge, I felt that I was good with shortcuts. I knew quite a few refactoring shortcuts, knew how to pause and stop program executing during debugging, was decent at jumping around between files, searching through my project, and all the other usual stuff. When I saw a co-worker using a shortcut that I didn&amp;rsquo;t know, I was quick to incorporate it into my workflow, too. After all, I was enough into shortcuts that I built a whole side-project around learning them.
&lt;strong&gt;The thing is, all new shortcuts that I learned were the ones that would augment my existing habits.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is a hen and egg problem; if you don&amp;rsquo;t know the shortcuts, you will not start to change your habits and use new IDE features because, without shortcuts, they are too tedious to use or not at all usable. But if you don&amp;rsquo;t form new habits, you will not learn the shortcuts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Without intending it, I think I found a way to break out of this cycle:
Learning all IDE shortcuts very well &lt;em&gt;before&lt;/em&gt; getting back to work. By learning &lt;em&gt;all&lt;/em&gt; shortcuts, you are not limited to what somebody shows you and might not be compatible with your situation. You can naturally include what makes sense, in addition to your existing skills.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The next section shows how this worked for me.&lt;/p&gt;
&lt;h1 id=&#34;evolving-my-habits&#34;&gt;Evolving my Habits&lt;/h1&gt;
&lt;p&gt;I started the learning process as it is usually done with KeyCombiner: By importing some shortcuts from the &lt;a href=&#34;https://keycombiner.com/collections/vscode/&#34;&gt;public collection of VSCode shortcuts&lt;/a&gt; into a personal collection. Intuitively, I started with the ones I already knew and some additional that seemed most useful to me. Back then I was still trying to show that I could learn all shortcuts very quickly with KeyCombiner. I didn&amp;rsquo;t expect that shortcuts that had nothing to do with my existing workflows will be the real deal.&lt;/p&gt;
&lt;p&gt;Some of the first combinations that I picked and that I did not know previously were &lt;kbd&gt;alt&lt;/kbd&gt;+&lt;kbd&gt;w&lt;/kbd&gt;/&lt;kbd&gt;r&lt;/kbd&gt;/&lt;kbd&gt;c&lt;/kbd&gt; for toggling find options (match whole word, regex, case sensitivity) because I was already using those features. This went on for a few days. I did a couple practice runs every day and added new shortcuts whenever KeyCombiner was saying that I mastered all of the previous ones.&lt;/p&gt;
&lt;p&gt;Eventually, I knew all shortcuts that seemed useful for how I was using VSCode back then. So I had to start adding shortcuts that had nothing to do with my existing habits. This brought up an interesting misconception. Often keyboard shortcuts are seen as a way to do things faster, compared to using the mouse. While that is true, I think there is an even more important aspect to shortcut usage in IDEs: Many features are not used at all, if you don&amp;rsquo;t know the shortcut!&lt;/p&gt;
&lt;p&gt;As I was learning new VSCode shortcuts every day, I started to use features that I previously didn&amp;rsquo;t even know of. One of the first things that suddenly made a lot of sense was &lt;em&gt;Select all Occurences of Find Match&lt;/em&gt; with &lt;kbd&gt;Alt&lt;/kbd&gt;+&lt;kbd&gt;Enter&lt;/kbd&gt; as default binding, especially when combined with regex search. I was so excited about it, that I even &lt;a href=&#34;https://twitter.com/ThomasKainrad/status/1305608337799761920&#34;&gt;tweeted a Gif&lt;/a&gt; showing how I use it to modify the CSV files that are the source for KeyCombiner&amp;rsquo;s public collection tables.&lt;/p&gt;
&lt;p&gt;The process worked both ways: Sometimes, while I was practicing a shortcut on KeyCombiner, it seemed apparent that this was useful and that I should include it into my workflow. Sometimes it hit me while coding that I had just learned a shortcut that can make the current task more efficient.&lt;/p&gt;
&lt;p&gt;In the following, I describe the most significant changes in my daily development habits. The list is far from complete though; there are many more small things that I picked up and frequently use now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Editor and Panel Management&lt;/strong&gt;&lt;br&gt;
Since knowing all the shortcuts to switch focus between panels and move editors around, I am using VSCode&amp;rsquo;s powerful layouting capabilities much more. I have 3-5 editor panels open at pretty much all times, seeing many related pieces of code at once. Working this way by using the mouse is very impractical.
I switch between editor panels with keyboard shortcuts and move editors from one group to the other.&lt;/p&gt;
&lt;p&gt;There is one negative side-effect, though: I am starting to get annoyed when I use other IDEs (PyCharm, Eclipse), because they lack far behind VSCode when it comes to using multiple editors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multi-cursor Editing&lt;/strong&gt;&lt;br&gt;
If you don&amp;rsquo;t know the shortcut to add additional cursors, I doubt you will ever use this incredibly powerful feature of VSCode. It is a typical feature that only blends into your habits once you know the shortcuts.
The basic commands are &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Alt&lt;/kbd&gt;+&lt;kbd&gt;Up&lt;/kbd&gt;/&lt;kbd&gt;Down&lt;/kbd&gt; for adding cursors above/below. However, I found the commands to &lt;em&gt;Insert cursor at end of each line selected&lt;/em&gt; (&lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;Alt&lt;/kbd&gt;+&lt;kbd&gt;i&lt;/kbd&gt;), and the above-mentioned &lt;em&gt;Select all Occurences of Find Match&lt;/em&gt; (&lt;kbd&gt;Alt&lt;/kbd&gt;+&lt;kbd&gt;Enter&lt;/kbd&gt;) even more powerful. These commands are why I frequently paste text snippets into VSCode, apply a few operations, and then copy-paste it back into whatever application I was using.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Opening views by shortcut&lt;/strong&gt;&lt;br&gt;
I sometimes tried to do this before, but it never stuck. Now I finally switch to all views (Explorer, Debug, Search, Problems, etc.) with dedicated keyboard shortcuts. These shortcuts translate very nicely to other IDEs. After learning the VSCode default bindings, which are &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;&amp;lt;char&amp;gt;&lt;/kbd&gt;, I set the same bindings also for Eclipse and PyCharm. Especially &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;e&lt;/kbd&gt; for switching to the explorer view and &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;d&lt;/kbd&gt; for switching to the debug view are among my most used shortcuts now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Making Selections&lt;/strong&gt;&lt;br&gt;
I hate to admit it, but I did not use shortcuts to expand and shrink AST selections before.  AST stands for &lt;em&gt;abstract syntax tree&lt;/em&gt;, which means that the IDE will take the current language&amp;rsquo;s syntax into account for modifying the selection. Finally, I picked up &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;left&lt;/kbd&gt;/&lt;kbd&gt;right&lt;/kbd&gt; to shrink and expand selections. Not all IDEs handle this in the same way, which can be a bit annoying, but it works well for the most frequent tasks, such as expanding the selection to enclose the current string, method, class, or HTML tag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Folding&lt;/strong&gt;&lt;br&gt;
I have started to use folding much more.  For years, I have rarely used this feature that all IDEs support to a great extent. Now, I do it frequently. Folding is one of these things that are just not feasible with the mouse. Of course, if you need to fold code, &lt;a href=&#34;https://blog.codinghorror.com/the-problem-with-code-folding/&#34;&gt;chances are that you need to structure your code better&lt;/a&gt;. Many organizations even apply automatic code linters that will complain when methods or classes are too long. However, I have never seen someone recommend splitting up blog posts into multiple files.  The same goes for other non-code file types, such as JSON. All we have there is folding.&lt;/p&gt;
&lt;p&gt;I am not saying these are the things you should start to learn now, far from it. These are the things I personally integrated deep into my workflows after doing this challenge, without choosing them deliberately, but naturally. Who knows what you will pick up.&lt;/p&gt;
&lt;p&gt;Of course, some of the shortcuts I learned still seem a bit, let&amp;rsquo;s say nonessential, but a little useless knowledge never killed nobody.&lt;/p&gt;
&lt;h1 id=&#34;more-unexpected-benefits&#34;&gt;More Unexpected Benefits&lt;/h1&gt;
&lt;p&gt;In addition to finally breaking out of old habits and experiencing a real evolution of my IDE usage, there were some other, less significant, but still delightful second-order effects.&lt;/p&gt;
&lt;p&gt;As a shortcut enthusiast, I often had the problem that I needed to decide which binding to use for a particular operation and application pair. KeyCombiner&amp;rsquo;s public shortcut search helps to find out which binding is the convention for a particular operation, but it can still be tricky. For example, take the frequently used &lt;em&gt;Step Over&lt;/em&gt; operation used for debugging. &lt;a href=&#34;https://keycombiner.com/collecting/collections/public/search/?description=step+over&amp;amp;keys=&amp;amp;mac_keys=&amp;amp;submit=Search&#34;&gt;KeyCombiner shows that VSCode, Eclipse, and JetBrains IDEs all use a different default binding&lt;/a&gt;(&lt;kbd&gt;F6&lt;/kbd&gt;,&lt;kbd&gt;F8&lt;/kbd&gt;,&lt;kbd&gt;F10&lt;/kbd&gt;). Learning every single VSCode shortcut has freed me from this mental load of deciding what the best binding for a particular IDE operation is. I simply use VSCode&amp;rsquo;s bindings everywhere. There are good arguments for why VSCode is a good reference. With the rise of GitHub CodeSpaces and its thriving extensions marketplace, I think it is likely that it will emerge as the industry standard of IDEs within a few years. That&amp;rsquo;s the topic for a future post, though.&lt;/p&gt;
&lt;p&gt;Another effect is that I rarely use the mouse anymore when working in VSCode. This is not ideological for me. I hold no grudges against mouse usage. It just so happens that the need to touch it rarely comes up anymore.
I guess most people that can do this use Vim or some Vim extension for their IDE. I can say confidently that it also works to learn all the default shortcuts. However, if you aim to eliminate the mouse completely while using VSCode, you should probably still go for one of the popular Vim emulators.&lt;/p&gt;
&lt;p&gt;And finally, maybe most importantly, VSCode feels like home now. As someone who also uses other IDEs daily, it&amp;rsquo;s hard to develop a feeling towards a particular one that this is &lt;em&gt;your&lt;/em&gt; professional tool: &lt;em&gt;The&lt;/em&gt; place where the magic happens. Now that I have a complete picture of VSCode&amp;rsquo;s feature set, I feel that I have truly made it my own.&lt;/p&gt;
&lt;h1 id=&#34;conclusions&#34;&gt;Conclusions&lt;/h1&gt;
&lt;p&gt;Of course, you don&amp;rsquo;t have to learn all shortcuts of your IDE. I never thought I would either. You can pick up a new shortcut here or there. Read blog posts where developers show off their selection of favorite shortcuts or exchange favorites with your co-workers. That&amp;rsquo;s a perfectly fine approach, and it is how a large majority of developers tackle this topic. It is even a core idea of my own project KeyCombiner to build collections of selected keyboard shortcuts that can be learned, shared, and organized.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;But&lt;/em&gt;, you can also invest a few hours spread over a couple of weeks, stop making compromises, and learn &lt;em&gt;all the shortcuts&lt;/em&gt;.
There is no way of knowing beforehand how this will influence your habits, how it will amend your existing workflows, and which entirely new possibilities will open up for you.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Implementing Paddle Payments for my Django SaaS</title>
      <link>https://tkainrad.dev/posts/implementing-paddle-payments-for-my-django-saas/</link>
      <pubDate>Thu, 08 Oct 2020 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/implementing-paddle-payments-for-my-django-saas/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;A couple of months ago, I published my SaaS side-project: &lt;a href=&#34;https://keycombiner.com&#34;&gt;https://keycombiner.com&lt;/a&gt;.&lt;br&gt;
It is a Django web application to organize, learn, and practice keyboard shortcuts! You can read more about the concept in &lt;a href=&#34;https://tkainrad.dev/tags/keycombiner/&#34;&gt;some of my other blog posts&lt;/a&gt;. This post describes how I implemented payment processing for KeyCombiner with &lt;a href=&#34;https://paddle.com/&#34;&gt;Paddle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Everybody loves Stripe. Their docs and tooling are excellent. However, they do not deal with the whole billing stack. Most of all, they do not handle taxes. Trying to figure out on your own which VAT rules apply for each of your customers and how to pay the respective sum to the correct authority is not feasible for a side-project.&lt;/p&gt;
&lt;p&gt;Paddle tackles this problem head-on by acting as a Merchant of Record (MOR). If you buy a subscription for KeyCombiner, you will not buy it from me, but from Paddle.&lt;/p&gt;
&lt;p&gt;The paddle documentation is quite good, but there is not that much third-party content going into more detail and providing step-by-step tutorials. This post describes, in great detail, one possible approach for accepting payments with Paddle in a Django app.&lt;/p&gt;
&lt;h1 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;A Paddle account. It can be created in a matter of minutes.&lt;/li&gt;
&lt;li&gt;A Django app that you want to monetize. This will probably take you a little longer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;setting-up-your-plan-in-paddle&#34;&gt;Setting up your Plan in Paddle&lt;/h1&gt;
&lt;p&gt;KeyCombiner has a basic pricing model. There is only one Pro subscription plan.
Before integrating such a plan into your Django web app, you need to create it. In your Paddle account, go to &lt;em&gt;Catalog→Subscription Plans→New Plan&lt;/em&gt;, configure it according to your needs, and hit Save.
We will later need the ID of the plan we just created. For this guide, I will use 487302. If you have an existing Paddle plan already, you &lt;em&gt;don&amp;rsquo;t&lt;/em&gt; need to create a new one, just note its ID.&lt;/p&gt;
&lt;h1 id=&#34;installing-and-configuring-dj-paddle&#34;&gt;Installing and Configuring dj-paddle&lt;/h1&gt;
&lt;p&gt;As Django developers, we naturally shy away from implementing anything ourselves.
There has to be an existing package for this, right?&lt;/p&gt;
&lt;p&gt;Fortunately, there is. It is called &lt;a href=&#34;https://github.com/paddle-python/dj-paddle&#34;&gt;dj-paddle&lt;/a&gt;, and I am grateful that &lt;a href=&#34;http://www.florian-purchess.de&#34;&gt;Florian Pruchess&lt;/a&gt; put in the effort to create it. It is a relatively new project, though, and does not provide everything out of the box, so we will get to do a little bit of coding ourselves, too.&lt;/p&gt;
&lt;p&gt;We will use &lt;code&gt;dj-paddle&lt;/code&gt; for a couple of things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;To provide the database models&lt;/li&gt;
&lt;li&gt;To provide service endpoints for Paddle&amp;rsquo;s webhooks&lt;/li&gt;
&lt;li&gt;To provide some convenience templates&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;p&gt;Start by installing the package:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;pip install dj&lt;span style=&#34;color:#555&#34;&gt;-&lt;/span&gt;paddle
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, add it to your &lt;code&gt;INSTALLED_APPS&lt;/code&gt; :&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;INSTALLED_APPS &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;(
    &lt;span style=&#34;color:#555&#34;&gt;...&lt;/span&gt;
    &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;djpaddle&amp;#34;&lt;/span&gt;,
    &lt;span style=&#34;color:#555&#34;&gt;...&lt;/span&gt;
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you have your settings split into production and development like a pro, I would recommend adding &lt;code&gt;djpaddle&lt;/code&gt; to your base settings file, as we can do some local testing with it.&lt;/p&gt;
&lt;p&gt;Then, add the following URL configuration to your &lt;code&gt;urls.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;path(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;paddle/&amp;#34;&lt;/span&gt;, include(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;djpaddle.urls&amp;#34;&lt;/span&gt;, namespace&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;djpaddle&amp;#34;&lt;/span&gt;))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is for the service endpoints that Paddle&amp;rsquo;s webhooks will contact whenever a subscription is created or modified. For this to work, we need to tell Paddle about our service URLs. In your Paddle account, head to &lt;em&gt;Developer Tools→Alerts/Webhooks&lt;/em&gt; and look for the &lt;em&gt;Receiving alerts&lt;/em&gt; section. There, you can enter your service URL that we just configured above. If you put the above line into your root &lt;code&gt;[urls.py](http://urls.py)&lt;/code&gt; it will be &lt;code&gt;&amp;lt;base-url/paddle/webhook/&amp;gt;.&lt;/code&gt; It&amp;rsquo;s also a good idea to specify an email address for receiving alerts. You don&amp;rsquo;t want to miss those sweet notifications about users purchasing subscriptions.&lt;/p&gt;
&lt;p&gt;Now it is time to add dj-paddle&amp;rsquo;s models to our database, run the migrations that come with the package:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;python manage&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;py migrate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The final thing we have to do before we can implement the actual checkout process is synching our Paddle Plans with our database. Fortunately, dj-paddle provides a management command that does just that by contacting Paddle&amp;rsquo;s web services. However, before we have to set up credentials so that our app can communicate with Paddle:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# can be found at https://vendors.paddle.com/authentication&lt;/span&gt;
DJPADDLE_VENDOR_ID &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;&amp;lt;your-vendor-id&amp;gt;&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# create one at https://vendors.paddle.com/authentication&lt;/span&gt;
DJPADDLE_API_KEY &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;&amp;lt;your-api-key&amp;gt;&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# can be found at https://vendors.paddle.com/public-key&lt;/span&gt;
DJPADDLE_PUBLIC_KEY &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;&amp;lt;your-public-key&amp;gt;&amp;#39;&lt;/span&gt;

&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# ID of the plan we created before&lt;/span&gt;
PADDLE_PLAN_ID &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f60&#34;&gt;487302&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, we are ready to run the management command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;python manage&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;py djpaddle_sync_plans_from_paddle
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;implementing-the-checkout&#34;&gt;Implementing the Checkout&lt;/h2&gt;
&lt;p&gt;Now, we can get to work. To implement our checkout process, we first need a checkout page. If you want to see mine, you are very welcome to create an account on &lt;a href=&#34;https://keycombiner.com&#34;&gt;https://keycombiner.com&lt;/a&gt; and check it out &lt;a href=&#34;https://keycombiner.com/users/account/checkout/&#34;&gt;here&lt;/a&gt;. Spoiler: It is loosely based on &lt;a href=&#34;https://getbootstrap.com/docs/4.0/examples/pricing/&#34;&gt;this basic Bootstrap pricing page template&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We don&amp;rsquo;t need much, though. Start by including PaddleJS, which is conveniently provided by dj-paddle:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;{&lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt; include &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;djpaddle_paddlejs.html&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And, of course, a button to trigger the checkout process:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#!&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;btn btn-primary paddle_button&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;data-theme&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;none&amp;#34;&lt;/span&gt;
    &lt;span style=&#34;color:#309&#34;&gt;data-product&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{ paddle_plan.id }}&amp;#34;&lt;/span&gt;
    &lt;span style=&#34;color:#309&#34;&gt;data-email&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{ user.email }}&amp;#34;&lt;/span&gt;&amp;gt;Purchase Subscription&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The corresponding class-based Django view looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#0a8;font-weight:bold&#34;&gt;Checkout&lt;/span&gt;(TemplateView):
    template_name &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;checkout.html&amp;#39;&lt;/span&gt;

    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;get_context_data&lt;/span&gt;(self, &lt;span style=&#34;color:#555&#34;&gt;**&lt;/span&gt;kwargs):
        context &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;super&lt;/span&gt;()&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;get_context_data(&lt;span style=&#34;color:#555&#34;&gt;**&lt;/span&gt;kwargs)
        context[&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;paddle_plan&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; Plan&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;objects&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;get(pk&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;settings&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;PADDLE_PLAN_ID)
        &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# If you have not added &amp;#39;djpaddle.context_processors.vendor_id&amp;#39; as a template context processors&lt;/span&gt;
        context[&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;DJPADDLE_VENDOR_ID&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; settings&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;DJPADDLE_VENDOR_ID
        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; context
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The crazy thing is, that is already everything you need for a basic checkout process. If you pair this checkout process with a method in your &lt;code&gt;User&lt;/code&gt; object, such as the following, you are ready to accept payments and restrict features for paying customers:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;has_subscription&lt;/span&gt;(self):
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;subscriptions&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;filter(status &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;active&amp;#39;&lt;/span&gt;)&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;exists()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, in your templates, do&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;{% if user.has_subscription %}
{% endif %}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;to restrict features for users with active subscriptions.&lt;/p&gt;
&lt;p&gt;Believe it or not, your customers can purchase subscriptions now, and Paddle will send you the money in recurring intervals.&lt;/p&gt;
&lt;p&gt;Unfortunately, as is often the case in software engineering, the final 10% of usability is 90% of the work. To make this convenient to use for your customers, read the &lt;a href=&#34;#polishing-the-checkout&#34;&gt;Polishing the Checkout&lt;/a&gt; section covering some frequent pain points with payment processing.&lt;/p&gt;
&lt;h2 id=&#34;testing-the-checkout&#34;&gt;Testing the Checkout&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&#34;https://paddle.com/support/how-do-i-test-my-checkout-integration/&#34;&gt;Paddle docs&lt;/a&gt; suggest a couple of different methods for testing your checkout process. In my experience, the easiest method is to create a 100% off coupon. To do this, go to &lt;em&gt;Catalog-&amp;gt;Coupons-&amp;gt;+ New Coupon&lt;/em&gt; in the Paddle web interface.&lt;/p&gt;
&lt;p&gt;Then, you can use the created coupon code to purchase a subscription without having to spend any money.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/checkout-with-coupon.png&#34;
         alt=&#34;Testing the KeyCombiner checkout.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Testing the KeyCombiner checkout.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This way, you can step through the whole checkout process and, most importantly, see what happens after you successfully purchased a subscription. With your current setup, you will probably need to wait for a moment and refresh the page until &lt;code&gt;user.has_subscription&lt;/code&gt; returns &lt;code&gt;True&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id=&#34;polishing-the-checkout&#34;&gt;Polishing the Checkout:&lt;/h1&gt;
&lt;p&gt;This section covers &lt;em&gt;everything&lt;/em&gt; I did to make the KeyCombiner checkout experience more convenient. If you have more complex problems, have a look at &lt;a href=&#34;https://dj-paddle.readthedocs.io&#34;&gt;dj-paddle&amp;rsquo;s documentation&lt;/a&gt;, the &lt;a href=&#34;https://github.com/paddle-python/paddle-client&#34;&gt;Paddle Client&lt;/a&gt; project, or, if things get really serious, &lt;a href=&#34;https://developer.paddle.com/api-reference/&#34;&gt;Paddle&amp;rsquo;s API reference documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;what-to-do-directly-after-checkout&#34;&gt;What to do directly after checkout?&lt;/h2&gt;
&lt;p&gt;With our current setup, we will only be aware of a new subscription once the webhook is triggered and the subscription object is added to our database. This can take a few moments.
Letting users wait for this time and hope that they refresh the page is not a good look. A user that just purchased a subscription has every right to feel like a &lt;em&gt;Pro&lt;/em&gt; right away.&lt;/p&gt;
&lt;p&gt;Fortunately, the creators of &lt;em&gt;dj-paddle&lt;/em&gt; came up with a solution. There is a template that will add data to the &lt;code&gt;Checkout&lt;/code&gt; model upon a successful completion of the checkout process. We only need to include it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;{&lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt; include &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;djpaddle_post_checkout.html&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can also redirect the user to a specific page after checkout:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;context[&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;djpaddle_checkout_success_redirect&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; reverse(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;users:checkout&amp;#39;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To make use of the &lt;code&gt;Checkout&lt;/code&gt; object that will now be in the database immediately after a successful purchase, we need to adapt our &lt;code&gt;has_subscription&lt;/code&gt; method. In addition to an active subscription, a recently completed checkout is enough to be considered a &lt;em&gt;Pro&lt;/em&gt; user on KeyCombiner:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;has_subscription&lt;/span&gt;(self):
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;subscriptions&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;filter(status&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;active&amp;#39;&lt;/span&gt;)&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;exists() &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;or&lt;/span&gt; Checkout&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;objects&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;filter(completed&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;True, email&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;email, created_at__day&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;now&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;day)&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;exists()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;let-a-user-manage-their-subscription&#34;&gt;Let a User Manage their Subscription&lt;/h2&gt;
&lt;p&gt;Unfortunately, some users don&amp;rsquo;t know what is good for them. So we need to allow them to cancel their subscription. Also, they should be able to update their payment details so they can ensure that the next payment is handled properly.&lt;/p&gt;
&lt;p&gt;I implemented this in the simplest form possible. By displaying Paddle&amp;rsquo;s update payment and cancellation URLs. Conveniently, these are present in dj-paddle&amp;rsquo;s &lt;code&gt;subscription&lt;/code&gt; model. To pass the user&amp;rsquo;s subscription to the template, modify the class-based &lt;code&gt;Checkout&lt;/code&gt; view:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;get_context_data&lt;/span&gt;(self, &lt;span style=&#34;color:#555&#34;&gt;**&lt;/span&gt;kwargs):
    &lt;span style=&#34;color:#555&#34;&gt;...&lt;/span&gt;
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;request&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;user&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;has_subscription:
        active_subscription &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;request&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;user&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;subscriptions&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;get(status&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;active&amp;#39;&lt;/span&gt;)
        context[&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;subscription&amp;#39;&lt;/span&gt;] &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; active_subscription
    &lt;span style=&#34;color:#555&#34;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;If you implement a &lt;a href=&#34;#handle-grace-period&#34;&gt;grace period as described below&lt;/a&gt;, you will need to update the code to retrieve the subscription from the database accordingly. Also make sure to show appropriate information to the user when they have no subscription but a recently completed checkout, as described &lt;a href=&#34;#what-to-do-directly-after-checkout&#34;&gt;above&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;Then, you can access the subscription&amp;rsquo;s &lt;code&gt;update_url&lt;/code&gt; and &lt;code&gt;cancel_url&lt;/code&gt; in your template:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{ subscription.update_url }}&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;btn btn-lg btn-primary&amp;#34;&lt;/span&gt;&amp;gt;
    Update Payment Method
&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;
&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{ subscription.cancel_url }}&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;class&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;btn btn-lg btn-danger&amp;#34;&lt;/span&gt;&amp;gt;
    Cancel Subscription
&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;a&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These URLs link to simple views provided and hosted by Paddle that offer the respective functionality. We can only rely on this simple approach because we listen to the subscription-related webhooks that keep our database models updated with the correct URLs.&lt;/p&gt;
&lt;h2 id=&#34;handle-grace-period&#34;&gt;Handle Grace Period&lt;/h2&gt;
&lt;p&gt;After a user cancels their subscription, they should still have access to the service until the end of their subscription period. I would have thought that Paddle has some API for this, but it appears that there is none. So, I &lt;a href=&#34;https://github.com/laravel/cashier-paddle/blob/641587f214f5400ad33d7d7b975cdaafda9dec41/src/Subscription.php&#34;&gt;copied the approach from Laravel Cashier&lt;/a&gt;, which is a Paddle integration project for the Laravel PHP framework. We simply use the next billing date field that remains the same even after a subscription is canceled:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;has_subscription&lt;/span&gt;(self):
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;subscriptions&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;filter(
        Q(status&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;active&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#555&#34;&gt;|&lt;/span&gt; Q(status&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;deleted&amp;#39;&lt;/span&gt;, next_bill_date__gte&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;now))&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;exists()
            &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;or&lt;/span&gt; Checkout&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;objects&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;filter(completed&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;True, email&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;email, created_at__day&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;now&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;day)&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;exists()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Stripe is often seen as the de-facto standard for accepting SaaS payments. Their documentation is universally praised, and integration is as simple as it gets. However, this post shows that integrating Paddle into your Django app isn&amp;rsquo;t rocket science either.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://imgs.xkcd.com/comics/coupon_code.png&#34;
         alt=&#34;The Paddle web interface lets you create coupon codes. I hope you feel inspired by this classic comic. (Source: xkcd.com)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The Paddle web interface lets you create coupon codes. I hope you feel inspired by this classic comic. (Source: xkcd.com)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If you want to see the described approach in practice, you are very welcome to create an account on &lt;a href=&#34;https://keycombiner.com&#34;&gt;KeyCombiner&lt;/a&gt; and purchase a Pro subscription ;)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How I learned 50 new keyboard shortcuts in 42 minutes</title>
      <link>https://tkainrad.dev/posts/how-i-learned-50-new-keyboard-shortcuts-in-42-minutes/</link>
      <pubDate>Tue, 21 Jul 2020 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/how-i-learned-50-new-keyboard-shortcuts-in-42-minutes/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;As developers, we frequently use keyboard shortcuts. Some enthusiasts know hundreds, others are contempt with the essential ones. But every developer does know some.  It is unthinkable to browse the Web, searching for a solution to your CSS alignment problem without using &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;f&lt;/kbd&gt;. Debugging would be tedious if we couldn&amp;rsquo;t pause and resume a program&amp;rsquo;s execution with the keyboard.&lt;/p&gt;
&lt;p&gt;In recent weeks, I have been able to significantly expand my keyboard shortcut knowledge with my new side-project web app &lt;a href=&#34;https://keycombiner.com&#34;&gt;KeyCombiner&lt;/a&gt;.&lt;br&gt;
In particular, I knew only a few shortcuts for the web-based tools I am using in my daily work. This post describes how it took me less than 1 hour to learn &lt;a href=&#34;https://keycombiner.com/collecting/collections/shared/76f3eec4-d45d-4d15-b521-6798addbba4e&#34;&gt;50 new key combinations&lt;/a&gt;. Fortunately, KeyCombiner keeps a detailed history of a user&amp;rsquo;s learning progress, so that I could write this post retrospectively.&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;Admittedly, the 42 minutes of learning time was interrupted by breaks, and the process involved some other tasks, such as creating the collection of shortcuts I wanted to learn. However, I did, in fact, spend only 42 minutes practicing the shortcuts and have had similar results with other shortcut collections.&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;setup&#34;&gt;Setup&lt;/h1&gt;
&lt;p&gt;The first step to learning new keyboard shortcuts is to define which. I don&amp;rsquo;t think it is efficient to try and learn all shortcuts for a particular application. You will end up with many that you do not use in your daily work and that you will soon forget again.&lt;/p&gt;
&lt;p&gt;Creating custom collections of keyboard shortcuts is perhaps the greatest strength of KeyCombiner and sets it apart from any other tool. Within minutes or less, you can have a personal collection by importing shortcuts from popular apps. I like to compare its approach to how you build playlists in music software. Instead of browsing your favorite artists’ albums, you browse categories of your favorite applications. Instead of adding songs to your playlists, you can add keyboard shortcuts to your collections.&lt;/p&gt;
&lt;p&gt;For this challenge, I created a new collection named &amp;ldquo;&lt;a href=&#34;https://keycombiner.com/collecting/collections/shared/76f3eec4-d45d-4d15-b521-6798addbba4e&#34;&gt;50 to learn&lt;/a&gt;&amp;rdquo;.
Then, I browsed KeyCombiner&amp;rsquo;s &lt;a href=&#34;https://keycombiner.com/collecting/collections/public/&#34;&gt;public collections&lt;/a&gt; and started to import everything I wanted to learn. As my goal was to get better with web application shortcuts, I focused on the public collections for Gmail, GitHub, GDrive, Docs, Slack, and Twitter. Importing the combinations into my new shortcut collection looked like this:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/how-i-learned-50-new/collecting.gif&#34;
         alt=&#34;Collecting shortcuts from KeyCombiner&amp;amp;rsquo;s public Gmail collection.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Collecting shortcuts from KeyCombiner&amp;rsquo;s &lt;a href=&#34;https://keycombiner.com/collecting/collections/public/56&#34;&gt;public Gmail collection&lt;/a&gt;.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Additionally, I added a few shortcuts for &lt;a href=&#34;https://www.syntevo.com/smartgit/&#34;&gt;Smartgit&lt;/a&gt; manually. It is a graphical Git client that I am using extensively. For some reason, I never learned its shortcuts. Unfortunately, KeyCombiner does not yet have a public shortcut collection for Smartgit that I could rely on. However, manual input does the job:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/how-i-learned-50-new/manual-collecting.gif&#34;
         alt=&#34;Manually adding keyboard shortcuts to my new collection.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Manually adding keyboard shortcuts to my new collection.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;You can browse the resulting collection with 50 keyboard shortcuts here: &lt;a href=&#34;https://keycombiner.com/collecting/collections/shared/76f3eec4-d45d-4d15-b521-6798addbba4e&#34;&gt;50 to learn&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I am afraid you will have to trust me that I did not know these shortcuts already before this experiment. However, as reassurance, you can check &lt;a href=&#34;https://tkainrad.dev/posts/a-collection-of-all-keyboard-shortcuts-i-use/&#34;&gt;my previous blog post&lt;/a&gt; covering all the shortcuts I was using until a few weeks ago. The list there does not include these new ones.&lt;/p&gt;
&lt;p&gt;To validate the success of this experiment, I will simply use KeyCombiner&amp;rsquo;s confidence metric. It will analyze my performance and tell me which shortcuts are already etched into my muscle memory during practice. So, the challenge will be completed successfully once this card says &lt;strong&gt;50/50&lt;/strong&gt;:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/how-i-learned-50-new/card-beginning.png&#34;
         alt=&#34;Overview of my new collection with 50 shortcuts to learn.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Overview of my new collection with 50 shortcuts to learn.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h1 id=&#34;learning-the-shortcuts&#34;&gt;Learning the shortcuts&lt;/h1&gt;
&lt;p&gt;Learning new shortcuts with KeyCombiner is dead simple. You click on the practice button for a particular collection, and the software does the rest. It will create 60-seconds training exercises where you are supposed to type the shortcuts of a collection as fast and as correct as possible.&lt;/p&gt;
&lt;p&gt;With every input, KeyCombiner remembers if it was correct and how long you took. It will use this information along with some machine learning to calculate a so-called confidence value for each key combination in your collections. A high confidence value means that you mastered a combination. Key combinations with a low value will occur more often in practice sessions, so you are not stuck repeating what you know already.
There are a few additional aspects to it, but the good thing is that users do not have to bother with the learning algorithm&amp;rsquo;s internal workings. Repeatedly doing practice runs is enough.&lt;/p&gt;
&lt;h2 id=&#34;starting-to-remember-the-keys&#34;&gt;Starting to remember the keys&lt;/h2&gt;
&lt;p&gt;The very beginning of learning a new collection is the hardest part. While I do not yet know any of the shortcuts, I have to rely on KeyCombiner&amp;rsquo;s delayed key hints to type any. Sometimes, I just skip combinations immediately to see what the keys are.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/how-i-learned-50-new/practice-start.gif&#34;
         alt=&#34;The very first practice runs with a new collection are quite slow.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The very first practice runs with a new collection are quite slow.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This is how results for such a practice run look like:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/how-i-learned-50-new/first-practice-run.png&#34;
         alt=&#34;Typical practice statistics for a collection you do not know yet.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Typical practice statistics for a collection you do not know yet.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Surprisingly, even in the first practice run, I got a couple of combinations right without key hints. This means that they occurred multiple times already or simply that I was able to guess them correctly. The latter is often the case for &lt;em&gt;go to&lt;/em&gt; shortcuts, which have been made popular by Gmail and are now used in many web applications.&lt;/p&gt;
&lt;p&gt;After a few practice runs, I started to disable key hints and only relied on skipping combinations where I couldn&amp;rsquo;t remember the keys. KeyCombiner will then display the skipped combination&amp;rsquo;s keys, so I have a shot at remembering them next time.&lt;/p&gt;
&lt;p&gt;The statistics for the 10th practice run looked as follows:&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/how-i-learned-50-new/after10.png&#34;
         alt=&#34;After just 10 minutes of practice, I already know some of the shortcuts.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;After just 10 minutes of practice, I already know some of the shortcuts.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;KeyCombiner recognizes 9 shortcuts as mastered. So far, so good.&lt;/p&gt;
&lt;h2 id=&#34;building-muscle-memory&#34;&gt;Building muscle memory&lt;/h2&gt;
&lt;p&gt;After another 10 practice runs, statistics showed that I knew already most of the shortcuts:&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/how-i-learned-50-new/after20.png&#34;
         alt=&#34;Statistics for my 20th practice run.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Statistics for my 20th practice run.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;However, KeyCombiner punishes errors quite heavily and is hesitant to show keys as mastered that I have just recently mistyped.
At this stage, it starts to get interesting to check the collection and see which specific shortcuts have very low confidence values. Usually, it is obvious to see some patterns:&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/how-i-learned-50-new/after20-problems.png&#34;
         alt=&#34;The combinations with lowest confidence values after 20 practice runs.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;The combinations with lowest confidence values after 20 practice runs.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The figure shows that I had problems knowing which modifiers to use for GSuite shortcuts (Gmail, GDrive and Docs). Some use &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Shift&lt;/kbd&gt; , some &lt;kbd&gt;Ctrl&lt;/kbd&gt; + &lt;kbd&gt;Alt&lt;/kbd&gt; and some don&amp;rsquo;t have a modifier at all (e.g. forwarding a mail in Gmail). I found that having a good look at such problems between practice runs can speed my progress, but it is not required.&lt;/p&gt;
&lt;p&gt;All that is needed is to keep hitting the practice button. At this stage, muscle memory starts to build, and the average time I need for each combination goes down quickly. KeyCombiner considers the average time metric for its confidence values.&lt;/p&gt;
&lt;h2 id=&#34;completing-the-challenge&#34;&gt;Completing the challenge&lt;/h2&gt;
&lt;p&gt;After 35 practice runs, things started to look very good:&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/how-i-learned-50-new/after35-lowest-confidence.png&#34;
         alt=&#34;Only few shortcuts with low confidence values remain.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Only few shortcuts with low confidence values remain.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;A confidence value of 3 means that I mastered a particular combination.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s have a look at the combination with the lowest confidence value. Surprisingly, it is the relatively simple &lt;kbd&gt;shift&lt;/kbd&gt;+&lt;kbd&gt;a&lt;/kbd&gt;:
&lt;figure&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/how-i-learned-50-new/after35-shift-a.png&#34;
         alt=&#34;Aggregated statistics for a specific combination.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Aggregated statistics for a specific combination.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;A combination is not guaranteed to appear in every practice. Furthermore, it can appear multiple times, especially when it has a low confidence value compared to the other shortcuts of a collection. Consequently, the above figure shows only 11 practice runs but 15 correct occurrences and 3 errors.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;I made quite a few mistakes with this combination in the beginning. Therefore KeyCombiner&amp;rsquo;s algorithm is skill skeptical about my skills. Errors usually mean that I thought I knew the shortcut but then typed a wrong combination. In this case, I suspect it is because I was hitting &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;a&lt;/kbd&gt; instead or missing the &lt;kbd&gt;a&lt;/kbd&gt; key. &lt;kbd&gt;shift&lt;/kbd&gt;+&lt;kbd&gt;a&lt;/kbd&gt; is a bit tricky to type if you are used to hit both &lt;kbd&gt;shift&lt;/kbd&gt; and &lt;kbd&gt;a&lt;/kbd&gt; with your little finger. However, my performance has been much better recently. Just a couple more practice runs to go and KeyCombiner will forgive my earlier mistakes.&lt;/p&gt;
&lt;p&gt;Then, finally, after another 7 practice runs, I had reached my goal:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/how-i-learned-50-new/card-success.png&#34;
         alt=&#34;Collection overview card after I have mastered all 50 shortcuts.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Collection overview card after I have mastered all 50 shortcuts.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Practicing on this collection looks much different now than in the beginning:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/how-i-learned-50-new/practice-end.gif&#34;
         alt=&#34;Pratice run after completing the challenge.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Pratice run after completing the challenge.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h1 id=&#34;tips&#34;&gt;Tips&lt;/h1&gt;
&lt;p&gt;It is straightforward to do a similar challenge. Simply go to &lt;a href=&#34;https://keycombiner.com&#34;&gt;https://keycombiner.com&lt;/a&gt;, create an account, import some shortcuts from public collections, and start practicing. However, I would like to share some additional
tips that make learning new shortcuts more pleasant for me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is a bit more fun in the beginning if you throw in some shortcuts that you already know. Even better, just extend one of your existing collections with new shortcuts that you want to learn. KeyCombiner will automatically show the new ones more frequently during practice.&lt;/li&gt;
&lt;li&gt;I sometimes activate key hints &lt;em&gt;without&lt;/em&gt; delay, which brings some variety to the learning experience and can help build muscle memory. If you are a good typist, you will be able to type a lot of shortcuts within the 60 seconds time limit.&lt;/li&gt;
&lt;li&gt;KeyCombiner will try to filter out shortcuts that cannot be captured during training. Those are marked with a warning icon in your collections. However, sometimes an invalid combination may still make it into your practice session, for example, if a browser extension does not pass it on to KeyCombiner. Simply append an underscore to any of the keys, e.g. &lt;kbd&gt;shift_&lt;/kbd&gt;+&lt;kbd&gt;a&lt;/kbd&gt;, so that KeyCombiner will recognize it as invalid.&lt;/li&gt;
&lt;li&gt;Depending on how frequently you use a shortcut in your daily work, your memory may fade over time. To counter this, I suggest practicing also mastered collections from time-to-time. A couple of one-minute practice sessions can be a nice break from work.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;results--conclusion&#34;&gt;Results &amp;amp; Conclusion&lt;/h1&gt;
&lt;p&gt;I have already learned many new keyboard shortcuts with KeyCombiner before this challenge. However, I never kept track of how long it took exactly until I knew them. I am thrilled with finding out that I could learn 50 new shortcuts in just 42 minutes of practice time.&lt;/p&gt;
&lt;p&gt;Believe it or not, without thinking much about it, I have started to use every single one of the 50 learned shortcuts in my daily work. I guess that&amp;rsquo;s the benefit of learning, not just any shortcuts, but precisely the ones you pick.&lt;/p&gt;
&lt;p&gt;Clearly, the experience will vary between people and also depend on the complexity of the shortcuts. Given that I spent much of my free time working with the keyboard shortcuts domain during the last months, I can probably pick up new ones faster than an average user. Still, I am confident that you could achieve similar results.&lt;/p&gt;
&lt;p&gt;I think this post makes a good argument that 42 minutes spent with &lt;a href=&#34;https://keycombiner.com&#34;&gt;https://keycombiner.com&lt;/a&gt; are an excellent investment in your productivity.&lt;/p&gt;
&lt;p&gt;If you are interested in following KeyCombiner&amp;rsquo;s evolution, please consider signing up for its newsletter using the form below.&lt;/p&gt;
&lt;div style=&#34;text-align: center;&#34;&gt;
&lt;script async data-uid=&#34;f71405db89&#34; src=&#34;https://tremendous-teacher-7449.ck.page/f71405db89/index.js&#34;&gt;&lt;/script&gt;
&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Why I built a new app for practicing keyboard shortcuts</title>
      <link>https://tkainrad.dev/posts/why-i-built-a-new-app-for-practicing-keyboard-shortcuts/</link>
      <pubDate>Wed, 08 Jul 2020 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/why-i-built-a-new-app-for-practicing-keyboard-shortcuts/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;I have always wanted to build a software product as a side project. Something that I could gradually improve and work on for years without deadlines and technology restrictions. Something that was &lt;em&gt;my&lt;/em&gt; side-project.
Fortunately, I was never short of ideas. In my post on &lt;a href=&#34;https://tkainrad.dev/posts/managing-my-personal-knowledge-base/#shortcut-database&#34;&gt;managing my personal knowledge base&lt;/a&gt;, I have mentioned that I keep a Notion database of project ideas. It has plenty of entries.&lt;/p&gt;
&lt;p&gt;In the same post, I also described my shortcut database use case. It was a spreadsheet to keep track of all the keyboard shortcuts I was using. Something about this motivated me much more than all the Slack bots and browser extensions lurking around in my ideas list. So, I started to work and expand on the concept in the form of a new web application. Initially, I thought It would take me until the end of 2020 to have anything that I could share with the world. Then, the Corona-lockdown came, and suddenly most of my other free time activities were no longer possible.&lt;/p&gt;
&lt;p&gt;Long story short, &lt;a href=&#34;http://keycombiner.com&#34;&gt;keycombiner.com&lt;/a&gt; is now available for everyone to use.
This post covers how it compares to existing tools, what it tries to do, how it does it, and the road ahead.&lt;/p&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;KeyCombiner is completely free to use, with no strings attached. For a demo of the &lt;em&gt;Practice&lt;/em&gt; mode or browsing &lt;a href=&#34;https://keycombiner.com/collecting/collections/public/&#34;&gt;public shortcut collections&lt;/a&gt;, you don&amp;rsquo;t even need to create an account.&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;existing-tools&#34;&gt;Existing Tools&lt;/h1&gt;
&lt;p&gt;There are a lot of typing practice tools available. However, KeyCombiner is quite different from all of them.&lt;/p&gt;
&lt;p&gt;The vast majority of existing tools focus on text typing. These applications will show you automatically generated pieces of text that you are supposed to type as fast and as correct as possible.
Usually, you will get a report at the end about your speed and accuracy. I do like this a lot and often test my typing speed. It is a fun thing to do once in a while, and if you realize that your typing speed is low, you might want to work on it.
Of course, these tools have nothing to do with keyboard shortcuts. They will not help with learning them and also do not include them in their typing practice.&lt;/p&gt;
&lt;p&gt;When looking at practicing keyboard shortcuts, there are far fewer alternatives to choose from. But still, there are a couple of existing apps.
To my knowledge, all of them work with pre-defined lessons, which is very unintuitive for me. I don&amp;rsquo;t want to learn &lt;em&gt;all&lt;/em&gt; keyboard shortcuts of a particular app. Also, they are focused solely on learning keyboard shortcuts, not with your typing skills per se. Therefore, they do not record typing speed, accuracy, or any other such metrics. For me, this alone removes most of the motivation for using such a tool. I don&amp;rsquo;t want to only learn shortcuts, I also want to become faster and more accurate at using them.&lt;/p&gt;
&lt;p&gt;KeyCombiner aims to bridge this gap between typing training software and shortcut learning apps. Besides, it goes to great lengths to make it easy to learn and practice not just any shortcuts but precisely the ones you want to use.
If you like to look at Venn diagrams that probably shouldn&amp;rsquo;t be Venn diagrams, I have just the thing for you:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/typing-trainers-venn.svg&#34;/&gt; 
&lt;/figure&gt;

&lt;h1 id=&#34;goals&#34;&gt;Goals&lt;/h1&gt;
&lt;p&gt;Developing KeyCombiner, I had and still have the following goals:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Enable efficient creation of keyboard shortcut collections&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The first goal is, of course, to cover the original use case described in my &lt;a href=&#34;https://tkainrad.dev/posts/managing-my-personal-knowledge-base/#shortcut-database&#34;&gt;knowledge management post&lt;/a&gt;.
Having an overview of all the keyboard shortcuts you are using and intend to learn is already useful in itself. However, it is clear that most people don&amp;rsquo;t want to spend a lot of time with this task, so it has to be possible in a couple of minutes to build meaningful collections.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Facilitate learning of keyboard shortcuts&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Generally, I want to provide as much value on top of a user&amp;rsquo;s shortcut collections as possible. Learning new keyboard shortcuts is probably the most powerful thing that can be done. It is therefore the second fundamental goal of KeyCombiner.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Allow to practice also text snippets&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As developers, we have many short text snippets that we need to remember. Think of &lt;em&gt;git&lt;/em&gt; commands or language syntax. These feel similar to keyboard shortcuts, and I would like to be able to practice them together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Help to improve typing speed and accuracy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Going beyond mere memorization of keyboard shortcuts is an important aspect and one thing that sets KeyCombiner apart from other software. The goal is not just to learn keyboard shortcuts, but to be able to type them fast and accurately, too.&lt;/p&gt;
&lt;h1 id=&#34;how-it-works&#34;&gt;How it works&lt;/h1&gt;
&lt;p&gt;This section covers KeyCombiner&amp;rsquo;s three main areas. However, those are very much connected, as illustrated in the following figure:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/tirangle-of-col-stat-train.svg&#34;/&gt; 
&lt;/figure&gt;

&lt;h2 id=&#34;create-shortcut-collections&#34;&gt;Create shortcut collections&lt;/h2&gt;
&lt;p&gt;A core idea of KeyCombiner is to learn and practice &lt;em&gt;exactly&lt;/em&gt; the shortcuts you need or want to use. The only way to achieve this is if you choose them yourself.&lt;/p&gt;
&lt;p&gt;I thought a lot about how to make this collection building process as efficient as possible. From the start, it was apparent that it would need to be possible to import keyboard shortcuts and text snippets from a public database of popular application shortcuts. This mechanic can be used to build the bulk of your collections quickly.  They can then be completed by manually adding entries.&lt;/p&gt;
&lt;p&gt;I like to compare my approach to how you build playlists in music software. Instead of browsing your favorite artists&#39; albums, KeyCombiner allows you to browse categories of your favorite applications. Instead of adding songs to your playlists, you can add keyboard shortcuts and text snippets to your collections:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/collecting.gif&#34;
         alt=&#34;KeyCombiner&amp;amp;rsquo;s combination tables support all popular multi-selection patterns, i.e. drag selecting, Shift-selection and maintaining selection via Ctrl&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;KeyCombiner&amp;rsquo;s combination tables support all popular multi-selection patterns, i.e. drag selecting, &lt;kbd&gt;Shift&lt;/kbd&gt;-selection and maintaining selection via &lt;kbd&gt;Ctrl&lt;/kbd&gt;&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;KeyCombiner&amp;rsquo;s &lt;a href=&#34;https://keycombiner.com/collecting/collections/public/&#34;&gt;public collections&lt;/a&gt; already contain thousands of keyboard shortcuts. Each collection can be filtered and searched quickly.&lt;/p&gt;
&lt;p&gt;Creating collections of keyboard shortcuts is already a use case in itself. It can help to answer a variety of questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How many shortcuts am I using?&lt;/li&gt;
&lt;li&gt;Are my key bindings logical and consistent or am I using completely different combinations for similar things?&lt;/li&gt;
&lt;li&gt;For which applications am I using my shortcuts?&lt;/li&gt;
&lt;li&gt;I am setting up a new machine and want to set up my key bindings. What were those exactly?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When combining personal shortcut collections with the other two main areas of KeyCombiner, we can answer even more interesting questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How well do I actually know the shortcuts in my collections?&lt;/li&gt;
&lt;li&gt;How fast can I type them?&lt;/li&gt;
&lt;li&gt;How often do I make an error while typing a specific shortcut?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As shown in the &lt;a href=&#34;#how-it-works&#34;&gt;illustration above&lt;/a&gt;, collections are annotated with information gathered from KeyCombiner&amp;rsquo;s statistics. Most importantly, the confidence value shows how good you are with a combination in your collections:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/confidence.png&#34;
         alt=&#34;Some keyboard shortcuts that I have practice often and am therefore very confident with.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Some keyboard shortcuts that I have practice often and am therefore very confident with.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;learn-shortcuts-flashcard-style&#34;&gt;Learn shortcuts flashcard-style&lt;/h2&gt;
&lt;p&gt;Before going deeper into confidence values and other statistical measures, we need to take a step back and look at how data is gathered. It is done while you practice your shortcut collections.&lt;/p&gt;
&lt;p&gt;In principle, KeyCombiner&amp;rsquo;s interactive training is similar to other applications. You are shown what a shortcut does and, ideally, you know the keys and type it in correctly:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/learning.gif&#34;
         alt=&#34;KeyCombiner&amp;amp;rsquo;s interactive trainer has a visual keyboard and options for displaying the keys to type.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;KeyCombiner&amp;rsquo;s interactive trainer has a visual keyboard and options for displaying the keys to type.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;What makes KeyCombiner unique in this regard is that you are practicing your very own collections, hence exactly the shortcuts you want to learn! Furthermore, KeyCombiner gathers detailed statistics that help to analyze your performance. You might know how many words you can type by minute (WPM), but do you also know how many keyboard shortcuts you can execute during this time?&lt;/p&gt;
&lt;p&gt;There are many more things to be said about the &lt;em&gt;Practice&lt;/em&gt; mode. It does all kinds of things to make learning as efficient as possible. For example, it uses ideas from spaced repetition and shows keyboard shortcuts with low confidence value more often than others. Then, there is the option to display the actual keys of a combination after a delay that gives you some time to think. This is a tricky thing to do because key combinations typed with hints should not influence the confidence value. But that&amp;rsquo;s a topic for another blog post.&lt;/p&gt;
&lt;p&gt;What I like to do personally is to gradually expand my collections. For example, I set a goal of learning 10 new shortcuts a given week. Then, at the beginning of the week, I browse the public collections for 10 new shortcuts and add them to one of my collections.
Throughout the week, I do a 60-seconds practice session from time to time. Because the new shortcuts have a low confidence value at the beginning, KeyCombiner will show them often during practice, and after just a couple of practice runs, I usually know them well.&lt;/p&gt;
&lt;h2 id=&#34;statistics-to-improve-accuracy--speed&#34;&gt;Statistics to improve accuracy &amp;amp; speed&lt;/h2&gt;
&lt;p&gt;One of the defined goals is to help users improve their typing skills beyond just remembering shortcuts. It is of similar importance to be able to type them fast and accurately. To help identify weaknesses and bad habits, KeyCombiner gathers detailed statistics during practice.&lt;/p&gt;
&lt;p&gt;The below figure shows an example bar chart for one of my early practice runs.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/test-run-stats.png&#34;
         alt=&#34;Interactive charts show which combinations need some further practice.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Interactive charts show which combinations need some further practice.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It is immediately obvious that I had problems typing &lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;a&lt;/kbd&gt;, &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Alt&lt;/kbd&gt;+&lt;kbd&gt;m&lt;/kbd&gt; and &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Shift&lt;/kbd&gt; + &lt;kbd&gt;c&lt;/kbd&gt;. Using such statistics, I actually found that I was occasionaly making mistakes with some of the most essential shortcuts: &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;x&lt;/kbd&gt;/&lt;kbd&gt;c&lt;/kbd&gt;/&lt;kbd&gt;v&lt;/kbd&gt;. This was because I used my pointer finger for each of them. After realizing that this was a problem, I started to use the middle finger for &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;x&lt;/kbd&gt; and am no longer making mistakes.&lt;/p&gt;
&lt;p&gt;When taking the average time into account, there are many more observations to make. If you find out which combinations are the fastest to type for you, you can use this knowledge and set such convenient combinations wherever possible.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/analytics.gif&#34;
         alt=&#34;KeyCombiner presents statistics per practice run and per key combination.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;KeyCombiner presents statistics per practice run and per key combination.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h1 id=&#34;roadmap&#34;&gt;Roadmap&lt;/h1&gt;
&lt;p&gt;The most pressing issue right now is user onboarding. Unfortunately, many people sign up and then never create any meaningful collections. Some import &lt;em&gt;all&lt;/em&gt; combinations of popular apps into a collection and quickly lose interest. This is not surprising because learning with all combinations of a public collection is not a good idea. It has all the problems of apps based on pre-defined drills but misses out on some of their benefits. Therefore, I am exploring possibilities to make the process of building your own collections even more obvious and seamless.&lt;/p&gt;
&lt;p&gt;After that, I will publish a desktop app. This will be nice because it will allow to train also shortcuts that are otherwise reserved for the browser, such as &lt;kbd&gt;Ctrl&lt;/kbd&gt;+(&lt;kbd&gt;Shift&lt;/kbd&gt;)+&lt;kbd&gt;N&lt;/kbd&gt;/&lt;kbd&gt;T&lt;/kbd&gt;/&lt;kbd&gt;W&lt;/kbd&gt;.&lt;/p&gt;
&lt;p&gt;Another important milestone in the relatively near future will be searching for shortcuts across all public collections. I am quite excited about this as I don&amp;rsquo;t think there is anything similar in any other app. How would you currently find out which applications offer &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;/kbd&gt;&lt;kbd&gt;Alt&lt;/kbd&gt;+&lt;kbd&gt;R&lt;/kbd&gt; as a keyboard shortcut? The question is relevant e.g., for developers thinking about which shortcuts to include in their applications.&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;Update from the future: All three items are now done.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User onboarding was improved by showing hints for new users, and by improving the spaced repetition algorithm to facilitate learning with large, unknown collections.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&#34;https://keycombiner.com/desktop/&#34;&gt;desktop app&lt;/a&gt; is available! In addition to enabling the practice of browser-reserved shortcuts, it comes with an instant look up for all shortcuts that are in your combined collections plus those of the currently active application.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&#34;https://keycombiner.com/collecting/collections/public/search/&#34;&gt;shortcut search&lt;/a&gt; is live and kicking! It works via URL parameters, so you can, for example, use &lt;a href=&#34;https://keycombiner.com/collecting/collections/public/search/?description=&amp;amp;keys=ctrl+enter&amp;amp;mac_keys=cmd+enter&amp;amp;submit=Search&#34;&gt;this link&lt;/a&gt; to see all shortcuts in KeyCombiner&amp;rsquo;s database that use &lt;kbd&gt;ctrl&lt;/kbd&gt;+&lt;kbd&gt;enter&lt;/kbd&gt; on Windows/Linux, and &lt;kbd&gt;cmd&lt;/kbd&gt;+&lt;kbd&gt;enter&lt;/kbd&gt; on macOS.&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I am very happy with the way my side project progressed, but there is a long road ahead. Keyboard shortcuts are a surprisingly rich domain.&lt;/p&gt;
&lt;p&gt;If the described concepts sound interesting to you, please go ahead and have a look at &lt;a href=&#34;https://keycombiner.com&#34;&gt;https://keycombiner.com&lt;/a&gt;. There is a demo of the interactive trainer directly on the landing page, and you can sign up for free with just an email address or log in with your Google account.&lt;/p&gt;
&lt;p&gt;I would love to hear what you think of it.&lt;/p&gt;
&lt;p&gt;Finally, if you are interested in following KeyCombiner&amp;rsquo;s evolution, please consider signing up to its newsletter using the form below.&lt;/p&gt;
&lt;div style=&#34;text-align: center;&#34;&gt;
&lt;script async data-uid=&#34;f71405db89&#34; src=&#34;https://tremendous-teacher-7449.ck.page/f71405db89/index.js&#34;&gt;&lt;/script&gt;
&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>A Collection of all Keyboard Shortcuts I use</title>
      <link>https://tkainrad.dev/posts/a-collection-of-all-keyboard-shortcuts-i-use/</link>
      <pubDate>Tue, 09 Jun 2020 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/a-collection-of-all-keyboard-shortcuts-i-use/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;I believe that typing proficiency is important for a software engineer. It is easier to stay in flow if you can type fast and navigate around your IDE and tools with shortcuts. In fact, I love that a part of our job has to do with manual skills. Improving these physical skills is a nice change from learning new technologies and tools.&lt;/p&gt;
&lt;p&gt;A good start for getting better at something is to find out where you stand.
At first, I wanted to get an overview of how many shortcuts I know and use.
So, last year, I created a database of my shortcuts in Notion. I already described this in &lt;a href=&#34;https://tkainrad.dev/posts/managing-my-personal-knowledge-base/#shortcut-database&#34;&gt;my post on personal knowledge management&lt;/a&gt;. In the process, I found some inconsistencies and tried to use the same key combination in as many applications as possible.&lt;/p&gt;
&lt;p&gt;Since then, I have thought &lt;em&gt;a lot&lt;/em&gt; more about this whole thing. I should probably not tell you that I had a few dreams where keyboard shortcuts played a prominent role. Anyway, I spent much of my social distancing free time on my first published solo project: &lt;a href=&#34;https://keycombiner.com&#34;&gt;KeyCombiner&lt;/a&gt;. It has made creating and maintaining my shortcut database much simpler.&lt;/p&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;This collection is also &lt;a href=&#34;https://keycombiner.com/collecting/collections/shared/89e47af4-0f2e-4d5d-98fa-6966bc7453cc&#34;&gt;shared and available via KeyCombiner&lt;/a&gt;, which allows for more complex filtering and searching and &lt;strong&gt;enables you to import selected shortcuts into your personal collections.&lt;/strong&gt;&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;The tables in this post are generated by using KeyCombiner&amp;rsquo;s CSV export and converting it to Markdown with &lt;a href=&#34;https://www.tablesgenerator.com/markdown_tables&#34;&gt;this handy web tool&lt;/a&gt;.
The collection is split by context. By context, I mean the application or type of applications in which the shortcut can be used.&lt;/p&gt;
&lt;h1 id=&#34;essential&#34;&gt;Essential&lt;/h1&gt;
&lt;p&gt;The context of these shortcuts is not further defined because I expect them to work anywhere. As the name suggests, I think these shortcuts are so ubiquitous that everyone should know them.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;New File&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;File Management&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;n&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open File&amp;hellip;&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;File Management&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;o&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Save&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;File Management&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;s&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Save As&amp;hellip;&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;File Management&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;s&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Close&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;File Management&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;w&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Print&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;General&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;p&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refresh&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;General&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;f5&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go Back&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;left&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go Forward&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;alt&lt;/kbd&gt;&lt;/kbd&gt; + &lt;kbd&gt;right&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;f&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cut selection&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;x&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy selection&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;c&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paste&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;v&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paste without formatting&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;v&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Undo&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;z&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redo&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;y&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to Beginning of Line&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;home&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to End of Line&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;end&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open a new tab, and jump to it&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Tab and window&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;t&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jump to the next open tab&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Tab and window&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;tab&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open a new window&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;Tab and window&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;n&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id=&#34;editor&#34;&gt;Editor&lt;/h1&gt;
&lt;p&gt;The next set of shortcuts is applicable in any kind of editing software. Except the first one (&lt;em&gt;Replace&lt;/em&gt;), these work in pretty much all text fields independent of application.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Replace&lt;/td&gt;
&lt;td&gt;Editor&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;h&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Left with Selection&lt;/td&gt;
&lt;td&gt;Editor&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;left&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Right with Selection&lt;/td&gt;
&lt;td&gt;Editor&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;right&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move to Line End with Selection&lt;/td&gt;
&lt;td&gt;Editor&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;end&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move to Line Start with Selection&lt;/td&gt;
&lt;td&gt;Editor&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;home&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move to Next Word&lt;/td&gt;
&lt;td&gt;Editor&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;right&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move to Next Word with Selection&lt;/td&gt;
&lt;td&gt;Editor&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;right&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move to Previous Word&lt;/td&gt;
&lt;td&gt;Editor&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;left&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move to Previous Word with Selection&lt;/td&gt;
&lt;td&gt;Editor&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;left&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indent Selection&lt;/td&gt;
&lt;td&gt;Editor&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;tab&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unindent Selection&lt;/td&gt;
&lt;td&gt;Editor&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;tab&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id=&#34;ide&#34;&gt;IDE&lt;/h1&gt;
&lt;p&gt;As a software engineer, this context is very important for me. In my experience, many complex IDE features, especially those related to refactoring, are not used at all if one does not know the shortcut. So in this case, knowing additional shortcuts does not only make you faster, it gives access to additional features.&lt;/p&gt;
&lt;p&gt;Those features can sometimes even have an impact on code quality. For example, if you know a shortcut to extract a selected code block into a method, you might be less prone to writing large blocks of code that are hard to read. Knowing the common debugging shortcuts can make you more comfortable with the process and less inclined to use &lt;code&gt;print&lt;/code&gt; statements.&lt;/p&gt;
&lt;p&gt;Currently, I use VSCode, PyCharm, and Eclipse as IDEs for different languages and project sizes. I have configured the listed shortcuts to work in all of them, except for some that I only use in PyCharm.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Go to File&amp;hellip;, Quick Open&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;p&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show command prompt&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;p&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open Resource&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;r&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Switch to recent file&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;e&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to symbol in current file (outline)&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;o&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to Symbol in Workspace&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;t&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to line number&amp;hellip;&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;l&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Format Document&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Rich Languages Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;f&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to Definition&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Rich Languages Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;f3&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to References&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Rich Languages Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;r&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rename Symbol&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Rich Languages Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;shift&lt;/kbd&gt; +  &lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;r&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search in all files/workspace&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Search&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;f&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start Debugging&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Debug&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;f11&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Continue&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Debug&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;f5&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mute Breakpoints&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Debug&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;f6&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Step Into&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Debug&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;f7&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Step Over&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Debug&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;f8&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stop Run/Debug&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Debug&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;f9&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move Line Down&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;down&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move Line Up&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;up&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy Line Down&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;down&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy Line Up&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;up&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete Line&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;d&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split editor horizontally&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Tab and window&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;t&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split editor vertically&lt;/td&gt;
&lt;td&gt;IDE&lt;/td&gt;
&lt;td&gt;Tab and window&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;d&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extract Method&lt;/td&gt;
&lt;td&gt;PyCharm&lt;/td&gt;
&lt;td&gt;Refactoring&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;m&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Introduce Variable&lt;/td&gt;
&lt;td&gt;PyCharm&lt;/td&gt;
&lt;td&gt;Refactoring&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;v&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Introduce Field&lt;/td&gt;
&lt;td&gt;PyCharm&lt;/td&gt;
&lt;td&gt;Refactoring&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;f&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Introduce Constant&lt;/td&gt;
&lt;td&gt;PyCharm&lt;/td&gt;
&lt;td&gt;Refactoring&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; +  &lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;c&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cut line (empty selection)&lt;/td&gt;
&lt;td&gt;PyCharm&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;x&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy line (empty selection)&lt;/td&gt;
&lt;td&gt;PyCharm&lt;/td&gt;
&lt;td&gt;Basic Editing&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;c&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id=&#34;os&#34;&gt;OS&lt;/h1&gt;
&lt;p&gt;The shortcuts in this section are among my most used ones. For anyone of these, I can say with certainty that I use it many times a day.&lt;/p&gt;
&lt;p&gt;You might notice the &lt;em&gt;Media&lt;/em&gt; shortcuts with keys, such as &lt;kbd&gt;num2&lt;/kbd&gt;. I repurposed my Numpad for this because I don&amp;rsquo;t use it for typing numbers. Reassigning keys works especially well with &lt;a target=&#34;_blank&#34; href=&#34;https://amzn.to/3keNGcL&#34;&gt;my keyboard with blank keycaps&lt;/a&gt; The labeling can never be wrong because there is none ;)&lt;/p&gt;
&lt;p&gt;The first entry in this context&amp;rsquo;s table represents 9 separate shortcuts. Switching between applications via &lt;kbd&gt;super&lt;/kbd&gt; + &lt;kbd&gt;1-9&lt;/kbd&gt; has been a noticeable productivity boost for me. I rarely have to start an application by other means now.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Open application 1-9&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;super&lt;/kbd&gt; + &lt;kbd&gt;1-9&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to previous application&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;Tab&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to screen on the left&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;super&lt;/kbd&gt; +  &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;Left&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move window to screen on the right&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;super&lt;/kbd&gt; +  &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;Right&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cycle through windows of current application&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;alt&lt;/kbd&gt; + &lt;kbd&gt;^&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Play (Music etc.)&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;num2&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Previous Track&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;num1&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next Track&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Media&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;num3&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mute Sound&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Audio&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;num5&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mute Mic&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Audio&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;num7&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Increase Volume (louder)&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Audio&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;num6&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Decrease Volume&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Audio&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;num9&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Put window to left half of monitor&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Window Management&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;super&lt;/kbd&gt; + &lt;kbd&gt;left&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Put window to right half of monitor&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Window Management&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;super&lt;/kbd&gt; + &lt;kbd&gt;right&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show Albert launcher&lt;/td&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;super&lt;/kbd&gt; +  &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;p&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id=&#34;chrome&#34;&gt;Chrome&lt;/h1&gt;
&lt;p&gt;Many of the shortcuts that I use most frequently while browsing the internet are already covered in the sections above. However, there are some keyboard combinations unique to web browsing.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Open the History page in a new tab&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Google Chrome features&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;h&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open the Downloads page in a new tab&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Google Chrome features&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;j&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jump to the address bar&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Address bar&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;l&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search with default search engine&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Address bar&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;k&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Return everything on the page to default size&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Webpages&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;0&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to the top of the page&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Webpages&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;home&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to the bottom of the page&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Webpages&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;end&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open a new window in Incognito mode&lt;/td&gt;
&lt;td&gt;Chrome&lt;/td&gt;
&lt;td&gt;Tab and window&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;n&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id=&#34;web-apps&#34;&gt;Web Apps&lt;/h1&gt;
&lt;p&gt;Until recently, I didn&amp;rsquo;t use a lot of web application keyboard shortcuts. I feel like the applications are partly to blame for this.
Why is it not common to show keyboard shortcuts in button tooltips? Or even in a small notification when a user does an action via mouse? Anyway, working on &lt;a href=&#34;https://keycombiner.com&#34;&gt;KeyCombiner&lt;/a&gt; motivated me to expand my shortcut knowledge in this area.&lt;/p&gt;
&lt;p&gt;I have some learning ahead of me, but I am not quite comfortable with my most used apps.
Knowing web application shortcuts is quite rewarding because the respective action would often take multiple mouse clicks. Sometimes it is even hard to find the right button for it, if you don&amp;rsquo;t know an action&amp;rsquo;s shortcut.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Keys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jump to a conversation&lt;/td&gt;
&lt;td&gt;Slack&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;k&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next unread channel or DM&lt;/td&gt;
&lt;td&gt;Slack&lt;/td&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;alt&lt;/kbd&gt; +  &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;down&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to the project home page (Project &amp;gt; Details).&lt;/td&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;Project&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;g&lt;/kbd&gt; &amp;gt; &lt;kbd&gt;p&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to the project issue boards list (Issues &amp;gt; Boards).&lt;/td&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;Project&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;g&lt;/kbd&gt; &amp;gt; &lt;kbd&gt;b&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to the project file search page.&lt;/td&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;Project&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;t&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to the project commits list (Repository &amp;gt; Commits).&lt;/td&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;Project&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;g&lt;/kbd&gt; &amp;gt; &lt;kbd&gt;c&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to the project merge requests list (Merge Requests).&lt;/td&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;Project&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;g&lt;/kbd&gt; &amp;gt; &lt;kbd&gt;m&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to the CI/CD jobs list (CI/CD &amp;gt; Jobs).&lt;/td&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;Project&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;g&lt;/kbd&gt; &amp;gt; &lt;kbd&gt;j&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to the project wiki (Wiki), if enabled.&lt;/td&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;Project&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;g&lt;/kbd&gt; &amp;gt; &lt;kbd&gt;w&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edit description.&lt;/td&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;Issues and Merge Requests&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;e&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change assignee.&lt;/td&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;Issues and Merge Requests&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;a&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change label.&lt;/td&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;Issues and Merge Requests&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;l&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edit wiki page.&lt;/td&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;Wiki pages&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;e&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rename selected item&lt;/td&gt;
&lt;td&gt;GDrive&lt;/td&gt;
&lt;td&gt;Take action on selected items&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;n&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to personal collections&lt;/td&gt;
&lt;td&gt;KeyCombiner&lt;/td&gt;
&lt;td&gt;Global Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;g&lt;/kbd&gt; &amp;gt; &lt;kbd&gt;c&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to public collections&lt;/td&gt;
&lt;td&gt;KeyCombiner&lt;/td&gt;
&lt;td&gt;Global Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;g&lt;/kbd&gt; &amp;gt; &lt;kbd&gt;p&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go to personal collection 1&lt;/td&gt;
&lt;td&gt;KeyCombiner&lt;/td&gt;
&lt;td&gt;Global Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;c&lt;/kbd&gt; &amp;gt; &lt;kbd&gt;1&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learn personal collection 1&lt;/td&gt;
&lt;td&gt;KeyCombiner&lt;/td&gt;
&lt;td&gt;Global Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;l&lt;/kbd&gt; &amp;gt; &lt;kbd&gt;1&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test your skills with personal collection 1&lt;/td&gt;
&lt;td&gt;KeyCombiner&lt;/td&gt;
&lt;td&gt;Global Navigation&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;t&lt;/kbd&gt; &amp;gt; &lt;kbd&gt;1&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skip current combination&lt;/td&gt;
&lt;td&gt;KeyCombiner&lt;/td&gt;
&lt;td&gt;During Learn or Test&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;esc&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learn current collection&lt;/td&gt;
&lt;td&gt;KeyCombiner&lt;/td&gt;
&lt;td&gt;Collections&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;l&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy selected combinations to personal collection 1&lt;/td&gt;
&lt;td&gt;KeyCombiner&lt;/td&gt;
&lt;td&gt;Collections&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;c&lt;/kbd&gt; &amp;gt; &lt;kbd&gt;1&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search or jump to a recently viewed page.&lt;/td&gt;
&lt;td&gt;Notion&lt;/td&gt;
&lt;td&gt;Most popular&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;p&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create a line break within a block of text&lt;/td&gt;
&lt;td&gt;Notion&lt;/td&gt;
&lt;td&gt;Create &amp;amp; style your content&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;enter&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bold selected text&lt;/td&gt;
&lt;td&gt;Notion&lt;/td&gt;
&lt;td&gt;Create &amp;amp; style your content&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;b&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Italicize selected text&lt;/td&gt;
&lt;td&gt;Notion&lt;/td&gt;
&lt;td&gt;Create &amp;amp; style your content&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;i&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turn selected text into inline code&lt;/td&gt;
&lt;td&gt;Notion&lt;/td&gt;
&lt;td&gt;Create &amp;amp; style your content&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;e&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate selected blocks&lt;/td&gt;
&lt;td&gt;Notion&lt;/td&gt;
&lt;td&gt;Edit &amp;amp; move blocks&lt;/td&gt;
&lt;td&gt;&lt;kbd&gt;ctrl&lt;/kbd&gt; + &lt;kbd&gt;d&lt;/kbd&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;It is probably not a good idea to copy my collection, but seeing what kind of shortcuts are used by another software engineer might give you some ideas. Maybe I could even inspire you to create a similar list of your own shortcuts. If so, you might want to try out &lt;a href=&#34;https://keycombiner.com&#34;&gt;KeyCombiner&lt;/a&gt;, as I am sure it&amp;rsquo;s the most efficient way of accomplishing this.&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;After you created such a collection of all the shortcuts you use, you might realize that a mere spreadsheet cannot give a full picture of your shortcuts skills. Many questions remain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How well do you actually know them?&lt;/li&gt;
&lt;li&gt;How fast can you type them?&lt;/li&gt;
&lt;li&gt;How often do you make an error while typing a shortcut?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://keycombiner.com&#34;&gt;KeyCombiner&lt;/a&gt; is built to answer precisely these questions with its interactive training features.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;Am I missing some important shortcuts? I would be very interested to hear your suggestions.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Using VueJS alongside Django</title>
      <link>https://tkainrad.dev/posts/use-vuejs-with-django/</link>
      <pubDate>Tue, 14 Apr 2020 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/use-vuejs-with-django/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;I am currently working on a very exciting project. The whole thing is quite challenging because the project&amp;rsquo;s scope is significant, and I am doing it alone in my spare time while working full time. So I have to be very efficient. Fortunately, I am using Django with its &lt;a href=&#34;https://docs.djangoproject.com/en/3.0/ref/contrib/&#34;&gt;&lt;em&gt;batteries-included&lt;/em&gt;&lt;/a&gt; approach.&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;The mentioned project is now in Open Beta! You can have a look at &lt;a href=&#34;https://keycombiner.com&#34;&gt;https://keycombiner.com&lt;/a&gt;.&lt;br&gt;
It is a web application to organize the keyboard shortcuts you use, get better at using them, and to learn new ones.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;I use all kinds of Django features that speed up my development, and I wouldn&amp;rsquo;t want to miss its template engine. Therefore, using Django only in the backend and building a JavaScript SPA for the frontend is not an option for me.&lt;br&gt;
However, even the most avid backend developer has to admit that some things warrant a client-side implementation. Small user actions should not require page reloads. Also, some parts of the web application I am building require rather sophisticated user interaction.&lt;/p&gt;
&lt;p&gt;Traditionally, one would mix Django with some jQuery to achieve the desired behavior. But there are newer JavaScript technologies now: &lt;a href=&#34;https://vuejs.org/&#34;&gt;React&lt;/a&gt; and &lt;a href=&#34;https://vuejs.org/&#34;&gt;Vue&lt;/a&gt;.&lt;br&gt;
Since our goal is to find a framework that we can use alongside Django without rethinking everything, we will go for Vue as the more lightweight alternative.
In this post, I will show that you can start to use Vue alongside Django&amp;rsquo;s template language with minimal effort.&lt;/p&gt;
&lt;h1 id=&#34;installation-and-setup&#34;&gt;Installation and Setup&lt;/h1&gt;
&lt;p&gt;One of the reasons to use Vue is its excellent &lt;a href=&#34;https://vuejs.org/v2/guide/&#34;&gt;documentation&lt;/a&gt;. It includes many examples, has a decent search, and a reasonably clear table of contents.&lt;/p&gt;
&lt;p&gt;This post aims to show that you can start to use Vue with your Django projects immediately without any sophisticated setup that will take hours to complete. Therefore, we will use the simplest method to use Vue.js: Including it via a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;&amp;lt;!-- development version, includes helpful console warnings --&amp;gt;&lt;/span&gt;
&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;script&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;https://cdn.jsdelivr.net/npm/vue/dist/vue.js&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That is it, we are now ready to create our first Vue.js instance:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&amp;lt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#309&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;app&amp;#34;&lt;/span&gt;&amp;gt;
  {{ message }}
&amp;lt;/&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;display:block;width:100%;background-color:#d8dada&#34;&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;6
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;var&lt;/span&gt; app &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;new&lt;/span&gt; Vue({
&lt;span style=&#34;display:block;width:100%;background-color:#d8dada&#34;&gt;  delimiters&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;[[&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;]]&amp;#34;&lt;/span&gt;],
&lt;/span&gt;  el&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;#app&amp;#39;&lt;/span&gt;,
  data&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; {
    message&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;Hello Vue!&amp;#39;&lt;/span&gt;
  }
})
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This example is taken from the official Getting Started Guide. However, there is one addition. Per default, Django and Vue use the same template tags. Hence, we need to set the Vue delimiters explicitly to avoid conflicts with Django&amp;rsquo;s template engine.&lt;/p&gt;
&lt;h1 id=&#34;access-django-data-from-vue&#34;&gt;Access Django Data from Vue&lt;/h1&gt;
&lt;p&gt;The simplest way to do so is the &lt;a href=&#34;https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#json-script&#34;&gt;built-in Django jscon_script filter&lt;/a&gt;.
This way you can immediately start using your Django models as data for your Vue instances.&lt;/p&gt;
&lt;p&gt;In HTML:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;{{ django_template_variable|json_script:&amp;#34;djangoData&amp;#34; }}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, in JavaScript, we load this data into a variable:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;let&lt;/span&gt; jsVariable &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; JSON.parse(&lt;span style=&#34;color:#366&#34;&gt;document&lt;/span&gt;.getElementById(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;djangoData&amp;#39;&lt;/span&gt;).textContent);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And it is ready to use with Vue:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;new&lt;/span&gt; Vue({
  delimiters&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;[[&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;]]&amp;#34;&lt;/span&gt;],
  el&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;#app&amp;#39;&lt;/span&gt;,
  data&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; jsVariable
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id=&#34;make-async-backend-requests&#34;&gt;Make async Backend Requests&lt;/h1&gt;
&lt;p&gt;One of the most frequent tasks of a Vue frontend is to make requests to a backend server application. With a full-stack Django application, we don&amp;rsquo;t have to do this for every user interaction. In some cases, a full page reload might be perfectly fine, and Django&amp;rsquo;s templating system provides all kinds of advantages. But to enhance user experience and to reap the full benefits of using Vue, we may still want to make backend requests in some places.&lt;/p&gt;
&lt;p&gt;Vue itself cannot handle requests. In this post, I will use &lt;a href=&#34;https://github.com/axios/axios&#34;&gt;axios&lt;/a&gt;, because it is also recommended in the official Vue Docs. Other alternatives are perfectly fine too.&lt;/p&gt;
&lt;p&gt;To pass Django&amp;rsquo;s CSRF protection mechanism, axios needs to include the respective cookie in its requests. The easiest way to accomplish this is to set global axios defaults:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;axios.defaults.xsrfCookieName &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;csrftoken&amp;#39;&lt;/span&gt;;
axios.defaults.xsrfHeaderName &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;X-CSRFTOKEN&amp;#34;&lt;/span&gt;;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Alternatively, we could also create an axios instance with the required settings:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;var&lt;/span&gt; instance &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; axios.create({
    xsrfCookieName&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;csrftoken&amp;#39;&lt;/span&gt;,
    xsrfHeaderName&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;X-CSRFTOKEN&amp;#34;&lt;/span&gt;,
});
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;Your Django template needs to contain the tag &lt;code&gt;{% csrf_token %}&lt;/code&gt; or, alternatively, the respective view must use the decorator &lt;a href=&#34;https://docs.djangoproject.com/en/3.0/ref/csrf/#django.views.decorators.csrf.ensure_csrf_cookie&#34;&gt;&lt;code&gt;ensure_csrf_cookie()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;The rest of Django&amp;rsquo;s default session backend for authentication will work out of the box, meaning that you can annotate your backend services with things like &lt;code&gt;loginRequired&lt;/code&gt; and it will just work.
To make the request, we can use axios as usual:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;axios.post(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;/django/backend/endpoint&amp;#39;&lt;/span&gt;, {
    data&lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; jsVariable 
})
    .then(&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; (response) {
        &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// handle response
&lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;&lt;/span&gt;    })
    .&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;catch&lt;/span&gt;(&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;function&lt;/span&gt; (error) {
        &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;// handle error
&lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;&lt;/span&gt;    });
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This call can be done within a Vue instance&amp;rsquo;s &lt;code&gt;mounted&lt;/code&gt; hook or any other place where you can put JavaScript code.&lt;/p&gt;

&lt;div class=&#34;notices warning&#34;&gt;
    &lt;p&gt;If you activated &lt;code&gt;CSRF_USE_SESSIONS&lt;/code&gt; or &lt;code&gt;CSRF_COOKIE_HTTPONLY&lt;/code&gt; in your Django settings, you need to read the CSRF token from the DOM. For more details, see &lt;a href=&#34;https://docs.djangoproject.com/en/3.0/ref/csrf/#acquiring-the-token-if-csrf-use-sessions-or-csrf-cookie-httponly-is-true&#34;&gt;the official Django docs&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;When you google for &lt;em&gt;Django + Vue&lt;/em&gt;, most results will be focused on using Django for your backend and Vue for a separate frontend project. Having two independent projects increases complexity, and you lose access to Django&amp;rsquo;s template system, which is a very powerful timesaver. On the other hand, access to a frontend framework such as Vue can do wonders for web applications that go beyond CRUD functionality.&lt;/p&gt;
&lt;p&gt;Fortunately, we do not need to decide between the two. This guide shows that you can have the cake and eat it too!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Add CSV Export to Wagtail&#39;s Modeladmin</title>
      <link>https://tkainrad.dev/posts/export-wagtail-modeladmin-tables-to-csv/</link>
      <pubDate>Thu, 06 Feb 2020 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/export-wagtail-modeladmin-tables-to-csv/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://wagtail.io/&#34;&gt;Wagtail&lt;/a&gt; is a modern open source CMS written in Python and based on Django. It is easy to integrate with existing Django projects. Apart from traditional CMS features, it provides a nice UI for managing any Django database model via the &lt;a href=&#34;https://docs.wagtail.io/en/v2.8/reference/contrib/modeladmin/&#34;&gt;modeladmin&lt;/a&gt; module.&lt;/p&gt;
&lt;p&gt;The modeladmin IndexView lists entries for a specific model in tabular form. It is easy to define which columns should be included. Starting from here, there are buttons for editing, creating and deleting entries.&lt;/p&gt;
&lt;p&gt;One feature is missing though: Data Export&lt;br&gt;
As the data is already presented in a table, CSV is an obvious export format.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/wagtail-csv-export/wagtail-export-button.png&#34;
         alt=&#34;We will add an additional button to the modeladmin IndexView&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;We will add an additional button to the modeladmin IndexView&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This idea is not entirely original. &lt;a href=&#34;https://parbhatpuri.com/add-download-csv-option-in-wagtail-modeladmin.html&#34;&gt;This blog post&lt;/a&gt; and &lt;a href=&#34;https://stackoverflow.com/questions/46206693/adding-a-button-to-wagtail-dashboard&#34;&gt;this StackOverflow question&lt;/a&gt; discuss the same thing and my code is heavily influenced by them. However, the solutions given at these sources are not quite ready to copy and paste, as they require some customization of the CSV exporting code for each model you want to export.&lt;/p&gt;
&lt;p&gt;The code given in this blog post can be used with any Django model. Per default, all columns are exported, but this can easily be customized on a per-model basis.&lt;/p&gt;
&lt;h1 id=&#34;implementation&#34;&gt;Implementation&lt;/h1&gt;
&lt;p&gt;I will cover the different implementation parts in detail. If you just want to copy-paste and get on with your life, that&amp;rsquo;s fine too. Just make sure you copy all the given code snippets. It is fine to put everything into &lt;code&gt;wagtail_hooks.py&lt;/code&gt;, except the HTML template.&lt;/p&gt;
&lt;h2 id=&#34;buttonhelper&#34;&gt;ButtonHelper&lt;/h2&gt;
&lt;p&gt;The first thing you need whenever you want to add custom functionality to Wagtail&amp;rsquo;s modeladmin is usually a &lt;code&gt;ButtonHelper&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#0a8;font-weight:bold&#34;&gt;ExportButtonHelper&lt;/span&gt;(ButtonHelper):
    export_button_classnames &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;icon&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;icon-download&amp;#39;&lt;/span&gt;]

    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;export_button&lt;/span&gt;(self, classnames_add&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;None, classnames_exclude&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;None):
        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; classnames_add &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;is&lt;/span&gt; None:
            classnames_add &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; []
        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; classnames_exclude &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;is&lt;/span&gt; None:
            classnames_exclude &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; []

        classnames &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;export_button_classnames &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; classnames_add
        cn &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;finalise_classname(classnames, classnames_exclude)
        text &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; _(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;Export {} to CSV&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;format(self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;verbose_name_plural&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;title()))

        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; {
            &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;url&amp;#39;&lt;/span&gt;: self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;url_helper&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;get_action_url(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;export&amp;#39;&lt;/span&gt;,
                                            query_params&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;request&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;GET),
            &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;label&amp;#39;&lt;/span&gt;: text,
            &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;classname&amp;#39;&lt;/span&gt;: cn,
            &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;title&amp;#39;&lt;/span&gt;: text,
        }
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Most of this code is just to get the CSS classes for the button right. The CSS classes &lt;code&gt;icon&lt;/code&gt; and &lt;code&gt;icon-download&lt;/code&gt; will ensure a simple button with a download icon and some text.&lt;/p&gt;
&lt;h2 id=&#34;adminurlhelper&#34;&gt;AdminURLHelper&lt;/h2&gt;
&lt;p&gt;Next, we need an AdminURLHelper that helps with generation, naming, and referencing of our new &lt;code&gt;export&lt;/code&gt; URL:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;21
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;22
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;23
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#0a8;font-weight:bold&#34;&gt;ExportAdminURLHelper&lt;/span&gt;(AdminURLHelper):
    non_object_specific_actions &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;create&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;choose_parent&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;index&amp;#39;&lt;/span&gt;,
                                    &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;export&amp;#39;&lt;/span&gt;)

    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;get_action_url&lt;/span&gt;(self, action, &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;args, &lt;span style=&#34;color:#555&#34;&gt;**&lt;/span&gt;kwargs):
        query_params &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; kwargs&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;pop(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;query_params&amp;#39;&lt;/span&gt;, None)

        url_name &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;get_action_url_name(action)
        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; action &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;in&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;non_object_specific_actions:
            url &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; reverse(url_name)
        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;else&lt;/span&gt;:
            url &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; reverse(url_name, args&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;args, kwargs&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;kwargs)

        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; query_params:
            url &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;?{params}&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;format(params&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;query_params&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;urlencode())

        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; url

        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;get_action_url_pattern&lt;/span&gt;(self, action):
        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; action &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;in&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;non_object_specific_actions:
            &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;_get_action_url_pattern(action)

        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;_get_object_specific_action_url_pattern(action)
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Once again, this looks a little more complicated than it is. We just need to add the &lt;code&gt;export&lt;/code&gt; action to the &lt;code&gt;non_object_specific_actions&lt;/code&gt;, because Wagtail treats actions as object-specific per default and will attempt to add the an object&amp;rsquo;s PK to the URL. Additionally, the URL helper appends the modeladmin filters to the action so that only the filtered data is exported.&lt;/p&gt;
&lt;h2 id=&#34;exportview&#34;&gt;ExportView&lt;/h2&gt;
&lt;p&gt;Finally, we need an &lt;code&gt;ExportView&lt;/code&gt; that implements the CSV export. For this, we will use some help from &lt;code&gt;django-queryset-csv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Install via&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;pip install django-queryset-csv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Using this, our view is very simple:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;display:block;width:100%;background-color:#d8dada&#34;&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;display:block;width:100%;background-color:#d8dada&#34;&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:block;width:100%;background-color:#d8dada&#34;&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;display:block;width:100%;background-color:#d8dada&#34;&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#0a8;font-weight:bold&#34;&gt;ExportView&lt;/span&gt;(IndexView):
&lt;span style=&#34;display:block;width:100%;background-color:#d8dada&#34;&gt;    model_admin &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; None
&lt;/span&gt;    
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;export_csv&lt;/span&gt;(self):
&lt;span style=&#34;display:block;width:100%;background-color:#d8dada&#34;&gt;        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; (self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;model_admin &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;is&lt;/span&gt; None) &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;hasattr&lt;/span&gt;(self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;model_admin,
&lt;/span&gt;&lt;span style=&#34;display:block;width:100%;background-color:#d8dada&#34;&gt;                                                        &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;csv_export_fields&amp;#39;&lt;/span&gt;):
&lt;/span&gt;            data &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;queryset&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;all()&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;values()
        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;else&lt;/span&gt;:
            data &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;queryset&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;all()&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;values(
&lt;span style=&#34;display:block;width:100%;background-color:#d8dada&#34;&gt;                &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;model_admin&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;csv_export_fields)
&lt;/span&gt;        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; render_to_csv_response(data)

    &lt;span style=&#34;color:#99f&#34;&gt;@method_decorator&lt;/span&gt;(login_required)
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;dispatch&lt;/span&gt;(self, request, &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;args, &lt;span style=&#34;color:#555&#34;&gt;**&lt;/span&gt;kwargs):
        &lt;span style=&#34;color:#366&#34;&gt;super&lt;/span&gt;()&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;dispatch(request, &lt;span style=&#34;color:#555&#34;&gt;*&lt;/span&gt;args, &lt;span style=&#34;color:#555&#34;&gt;**&lt;/span&gt;kwargs)
        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;export_csv()
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;It is worth to note the &lt;code&gt;model_admin&lt;/code&gt; field. We will use this for specifying a custom list of exported fields. Lines 5 and 6 make sure that whenever &lt;code&gt;model_admin&lt;/code&gt; is set and the &lt;code&gt;csv_export_fields&lt;/code&gt; attribute is given, the custom field list is used instead of the default behavior that just exports all fields.&lt;/p&gt;
&lt;h2 id=&#34;mixin&#34;&gt;Mixin&lt;/h2&gt;
&lt;p&gt;Making use of Python&amp;rsquo;s Multiple-Inheritance system, we can create a Mixin that we will later use to override  &lt;code&gt;button_helper_class&lt;/code&gt;, &lt;code&gt;url_helper_class&lt;/code&gt;, &lt;code&gt;export_view_class&lt;/code&gt; and &lt;code&gt;get_admin_urls_for_registration&lt;/code&gt; all at once:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#0a8;font-weight:bold&#34;&gt;ExportModelAdminMixin&lt;/span&gt;(&lt;span style=&#34;color:#366&#34;&gt;object&lt;/span&gt;):
    button_helper_class &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; ExportButtonHelper
    url_helper_class &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; ExportAdminURLHelper
    export_view_class &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; ExportView

    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;get_admin_urls_for_registration&lt;/span&gt;(self):
        urls &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#366&#34;&gt;super&lt;/span&gt;()&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;get_admin_urls_for_registration()
        urls &lt;span style=&#34;color:#555&#34;&gt;+=&lt;/span&gt; (url(self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;url_helper&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;get_action_url_pattern(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;export&amp;#39;&lt;/span&gt;),
                        self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;export_view,
                        name&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;url_helper&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;get_action_url_name(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;export&amp;#39;&lt;/span&gt;)), )
        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; urls

    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;export_view&lt;/span&gt;(self, request):
        kwargs &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;model_admin&amp;#39;&lt;/span&gt;: self}
        view_class &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;export_view_class
        &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; view_class&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;as_view(&lt;span style=&#34;color:#555&#34;&gt;**&lt;/span&gt;kwargs)(request)
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;html-template&#34;&gt;HTML Template&lt;/h2&gt;
&lt;p&gt;Now, we need to create an HTML template that includes our new button plus the original modeladmin buttons:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;{&lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt; extends &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;modeladmin/index.html&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt;}

{&lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt; block header_extra &lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt;}
    {&lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt; include &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;modeladmin/includes/button.html&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;with&lt;/span&gt; button&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;view&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;button_helper&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;export_button &lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt;}
    {{ block&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;super }}{&lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt; comment &lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt;}Display the original buttons {&lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt; endcomment &lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt;}
{&lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt; endblock &lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt;}
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;enabling-csv-export-for-models&#34;&gt;Enabling CSV Export for Models&lt;/h2&gt;
&lt;p&gt;To make a modeladmin table exportable, just add the mixin to your ModelAdmin definitions in &lt;code&gt;wagtail_hooks.py&lt;/code&gt; and set the &lt;code&gt;index_template_name&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#0a8;font-weight:bold&#34;&gt;FooModelAdmin&lt;/span&gt;(ExportModelAdminMixin, ModelAdmin):
    index_template_name &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;wagtailadmin/export_csv.html&amp;#34;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you want to customize the CSV columns, you can use our new optionl &lt;code&gt;csv_export_fields&lt;/code&gt; attribute. It even allows to export attributes of related tables using regular Django ORM syntax (&lt;code&gt;__&lt;/code&gt;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#0a8;font-weight:bold&#34;&gt;FooModelAdmin&lt;/span&gt;(ExportModelAdminMixin, ModelAdmin):
    index_template_name &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;wagtailadmin/export_admin.html&amp;#34;&lt;/span&gt;
        csv_export_fields &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; [
        &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;foobar&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;other_model__attribute&amp;#39;&lt;/span&gt;
    ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;In 2020, Wagtail is almost certainly the best option to add CMS functionality to a Django project. This post illustrates its extensibility. In a couple of minutes, you can enable CSV export for all your Django models.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Managing my personal knowledge base</title>
      <link>https://tkainrad.dev/posts/managing-my-personal-knowledge-base/</link>
      <pubDate>Sun, 05 Jan 2020 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/managing-my-personal-knowledge-base/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;It is hard to imagine any other field where lifelong learning is more important than in software engineering. Another unique characteristic is the degree to which learning material is available for free on the internet. On top of that, we create various resources ourselves by documenting issues, submitting bug reports, writing notes, creating documentation, and many others. The sum of all these resources can be called a knowledge base.
You could argue that every developer has a system to manage their personal knowledge base, whether they know it or not. In this post, I explain my knowledge management practices.&lt;/p&gt;

&lt;div class=&#34;notices warning&#34;&gt;
    &lt;p&gt;A software engineer&amp;rsquo;s personal knowledge base is likely to overlap with the knowledge of their employers and project partners. Be sure to carefully study your data protection obligations. A good basis is to keep your personal knowledge base strictly technical and to never include data that is related to customers, or people in general.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;I chose this topic mainly because of three reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Fun:&lt;/strong&gt;&lt;br&gt;
I enjoy thinking about my workflows and trying to improve them. Maybe a little too much, some friends have started to roll their eyes when I try to recommend a new tool to them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improving my system and workflows:&lt;/strong&gt;&lt;br&gt;
Writing &lt;a href=&#34;https://tkainrad.dev/posts/setting-up-linux-workstation/&#34;&gt;a post about my Linux setup&lt;/a&gt; has been very rewarding for me. While documenting my configuration, especially my command-line workflows, I identified some shortcomings that I have since eliminated. After writing this article, I can already say that my knowledge management workflows have improved similarly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Relevance:&lt;/strong&gt;&lt;br&gt;
Knowledge management in all its forms is integral to software engineering.&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://imgs.xkcd.com/comics/is_it_worth_the_time.png&#34;
         alt=&#34;Knowledge management tasks are done frequently. It pays off to do them efficiently. (Source: xkcd.com)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Knowledge management tasks are done frequently. It pays off to do them efficiently. (Source: xkcd.com)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Much of this post comes down to describing my usage of software tools. The stars will be&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.notion.so/?r=cb45b9cefd824015a044b3336009be32&#34;&gt;Notion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gitlab.com/&#34;&gt;GitLab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://workona.com/&#34;&gt;Workona&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gitlab.com/&#34;&gt;Dropbox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/albertlauncher/albert&#34;&gt;Albert&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;Copying all of my practices will likely not serve you well. I invite you to try out things at your own pace and revisit this post whenever you are looking for new ideas.&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;bookmarking&#34;&gt;Bookmarking&lt;/h1&gt;
&lt;p&gt;Even though everybody loves books, or at least says so, myself included, the reality is that we rely on digital resources most of the time.
The challenge is to organize this knowledge efficiently without creating too much overhead.&lt;/p&gt;
&lt;p&gt;There are some naive approaches that I do not deem sufficient. Until a few years ago, I relied mainly on browser bookmarks, but for some reason, browsers do not provide proper organizing features.
Another way is to just copy the URLs into notes, project issues, documentation and all other kinds of manually created content. This is viable and we all do it regularly, but it has nothing to do with what I think is bookmark management.&lt;/p&gt;
&lt;p&gt;My current setup is a little more complex, but in the end, it does not require more work than keeping bookmarks solely in the browser.
I organize my bookmarks in multiple layers, depending on how often I want to access them and how long they should persist.&lt;/p&gt;
&lt;p&gt;Maybe you are thinking of caching now, but fortunately, bookmarking layers are not caching levels. This would be hard to manage because as we all know, there are &lt;a href=&#34;https://martinfowler.com/bliki/TwoHardThings.html&#34;&gt;two hard problems in computer science&lt;/a&gt;: cache invalidation, naming things, and off-by-1 errors.
In contrast to cache entries, a bookmark is not supposed to move between layers. Instead, it gets inserted at the right place and stays there.&lt;/p&gt;
&lt;p&gt;The following figure illustrates my layers:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/managing-my-personal-knowledge-base/bookmarking-layers.png&#34;
         alt=&#34;Bookmarking layers&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Bookmarking layers&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;1-chrome-bookmarks&#34;&gt;1. Chrome Bookmarks&lt;/h2&gt;
&lt;p&gt;Chrome bookmarks have some nice benefits. They are synchronized between devices, new ones can be added easily, and organizing them in folders brings at least some structure. Another advantage for me is the &lt;a href=&#34;https://albertlauncher.github.io/docs/extensions/chromium/&#34;&gt;Albert Chromium extension&lt;/a&gt; that lets me access and open my Chrome bookmarks from Albert:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/managing-my-personal-knowledge-base/albert-chrome-bookmarks.gif&#34;
         alt=&#34;Searching through Chrome bookmarks directly from Albert. Pressing enter opens the selected entry in Chrome.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Searching through Chrome bookmarks directly from Albert. Pressing enter opens the selected entry in Chrome.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Because of these advantages, Chrome bookmarks are ideal for things that I need often and want to have accessible somewhat independent of context. They are, however, not well suited for keeping extensive bookmark libraries. There is no tagging, it is not possible to add comments, and browsers do not even save a timestamp for when a page was bookmarked. It is further not possible to filter and search Chrome bookmarks using complex queries. In my experience, browser bookmark libraries are hard to manage if they grow beyond a certain size.&lt;/p&gt;
&lt;p&gt;Some examples of well-suited Chrome bookmarks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS EC2 dashboard&lt;/li&gt;
&lt;li&gt;Google cloud platform console&lt;/li&gt;
&lt;li&gt;Slack workspaces&lt;/li&gt;
&lt;li&gt;GitLab boards&lt;/li&gt;
&lt;li&gt;Content sites, such as &lt;a href=&#34;https://news.ycombinator.com/&#34;&gt;Hacker News&lt;/a&gt;, &lt;a href=&#34;https://www.infoq.com/&#34;&gt;InfoQ&lt;/a&gt;, and &lt;a href=&#34;https://dzone.com/&#34;&gt;DZone&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Examples of things that &lt;strong&gt;should not be&lt;/strong&gt; Chrome bookmarks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Blog posts to be read later&lt;/li&gt;
&lt;li&gt;Links to Open Source Projects&lt;/li&gt;
&lt;li&gt;Stack Overflow questions&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;2-workona&#34;&gt;2. Workona&lt;/h2&gt;
&lt;p&gt;Workona is a relatively new addition to my toolbox. I have mixed feelings about it, mainly because it can feel a little slow at times.
There are some things it does very well though.&lt;/p&gt;
&lt;p&gt;Workona lets you create browser workspaces that come with their own list of bookmarks. This works nicely when you have multiple projects that you work on for longer periods. It will also remember which tabs you had open in a specific workspace and synchronize this information across devices. It is, therefore, a nice place for things that I need often &lt;em&gt;dependent&lt;/em&gt; on context.&lt;/p&gt;
&lt;figure class=&#34;&amp;#39;center-figure&amp;#39;&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/managing-my-personal-knowledge-base/workona-workspace.jpg&#34;
         alt=&#34;Screenshot of a Workona Workspace (Source)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Screenshot of a Workona Workspace (&lt;a href=&#34;https://workona.com/tour/&#34;&gt;Source&lt;/a&gt;)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Workona further allows you to add apps and will then collect links and even features for those apps to be quickly accessible. For example, I use it to access my different GitLab and GitHub projects, Slack Workspaces, and sometimes even StackOverflow questions. These entries are automatically created by Workona and therefore I do not see them as bookmarks. It is also possible to access features of these apps directly from Workona, mainly to create new resources, such as GitLab/GitHub projects, Google Docs, DropBox Files and so on. It feels a little bit like &lt;a href=&#34;https://getstation.com/&#34;&gt;Station&lt;/a&gt; built directly into the browser.&lt;/p&gt;
&lt;p&gt;The selling point for me is that Workona &lt;strong&gt;lets me do all of the described things via shortcuts&lt;/strong&gt; and it is even possible to customize these shortcuts at &lt;code&gt;chrome://extensions/shortcuts&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;3-notion&#34;&gt;3. Notion&lt;/h2&gt;
&lt;p&gt;Finally, we arrive at the layer that is suited for maintaining a large library of bookmarks. For this, and many other use cases, I use &lt;a href=&#34;https://www.notion.so/?r=cb45b9cefd824015a044b3336009be32&#34;&gt;Notion&lt;/a&gt;. It has become the core of my personal knowledge base.&lt;/p&gt;

&lt;div class=&#34;notices warning&#34;&gt;
    &lt;p&gt;Notion&amp;rsquo;s free tier comes with a hard limit on the number of blocks you can create. This means that you will have to switch to the paid version if you use it seriously for a while. It is free for &lt;a href=&#34;https://www.notion.so/Notion-for-students-teachers-adc631df15ee4ab9a7a33dd50f4c16fe&#34;&gt;students and teachers&lt;/a&gt;.&lt;br&gt;
All links to Notion in this post are affiliate links. If you sign up for an account through these links, you will get 10$ of free Notion credit and I will get 5$. This works even if you create a free account.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;The killer feature for me is the database. It is amazingly flexible and is single-handedly able to replace multiple other tools for me. For my bookmark collection, I use a single large database. Adding new items can be done via the &lt;a href=&#34;https://chrome.google.com/webstore/detail/notion-web-clipper/knheggckgoiihginacbkhaalnibhilkk&#34;&gt;Notion Web Clipper&lt;/a&gt;. I am a little annoyed that the web clipper doesn&amp;rsquo;t let me add properties and tags directly, but other than that, it works well. Previously, I used Trello, which was also quite good at keeping bookmarks. However, to limit the number of different tools I use, I replaced it with Notion.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/managing-my-personal-knowledge-base/notion-bookmark-database.png&#34;
         alt=&#34;My bookmarks database filtered to show only entries related to this post.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;My bookmarks database filtered to show only entries related to this post.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The goal with this database is that I can find something years from now with only a vague memory that it should be there. This is possible because Notion automatically stores metadata, such as a creation timestamp. Even more important, I can add tags and arbitrary properties.&lt;/p&gt;
&lt;p&gt;Things get really exciting once you use relations between different Notion databases. As an example, I have a database of my blog posts that has a relation to my bookmarks. I can now filter my bookmarks by blog posts and quickly see which sources influenced a particular post.&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;I cannot cover all Notion database features in this post. I think this is something you have to see for yourself. The &lt;a href=&#34;https://www.notion.so/Intro-to-databases-fd8cd2d212f74c50954c11086d85997e&#34;&gt;Notion docs page about the topic&lt;/a&gt; might give you some further impressions though.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;The downside of this rich feature palette is that it requires some discipline. I tag and annotate new entries, that I added via the web clipper, about twice a month. Often, I just delete new items because they do not seem important anymore. If I can not think of appropriate tags or relations for an item, this often means that it is not very relevant to me.&lt;/p&gt;
&lt;p&gt;Another important lesson I had to learn the hard way:&lt;br&gt;
Do not use too many databases in parallel. Notion provides excellent methods for filtering, and searching tables. You can even define different views onto the same database that only shows a specific part of the data. It is therefore not required to separate all kinds of things into different databases. For example, at first, I had three different databases for Python resources, Django resources, and Wagtail resources. This was a bad solution. Now, these all live in the same database with appropriate tags.&lt;/p&gt;
&lt;p&gt;I hope that someday I will be able to search through my Notion databases from &lt;a href=&#34;https://github.com/albertlauncher/albert&#34;&gt;Albert&lt;/a&gt;. Maybe I will build an extension myself once the Notion API is finally released.&lt;/p&gt;
&lt;p&gt;To illustrate what I have described in probably too many words, you can have a look at a &lt;a href=&#34;https://www.notion.so/tkpersonal/047517457f70471a91a9dac793b8d51b?v=802c2ec373fa43d0b7caf16ed8bfccda&#34;&gt;public fraction of my bookmarks database&lt;/a&gt; containing all the resources that were helpful for writing this post. The post-column in this public database appears empty because the related table is not public.&lt;/p&gt;
&lt;h2 id=&#34;4-native-bookmark-sources&#34;&gt;4. Native Bookmark Sources&lt;/h2&gt;
&lt;p&gt;The last layer does not require any work at all. It only means to be aware of native bookmark sources when trying to find something, and also when thinking about adding new bookmarks. For example, it is quite easy to search through questions you have answered on Stack Overflow. It is also not a problem to go back to your Hacker News posts or search through projects you starred on GitHub. Keeping those things in your own bookmark sources adds redundancy and noise.&lt;br&gt;
These are some of my most commonly used native bookmark sources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://news.ycombinator.com/&#34;&gt;https://news.ycombinator.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://twitter.com/&#34;&gt;https://twitter.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/&#34;&gt;https://github.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gitlab.com/&#34;&gt;https://gitlab.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://tkainrad.dev/posts/&#34;&gt;https://tkainrad.dev/posts/&lt;/a&gt; ;-)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;
&lt;p&gt;I am aware that this sounds immensely complicated. However, I stick to my statement that it does not actually require more work than keeping all bookmarks in the browser. You still have to add every bookmark only once. If you internalize your system, you will not have problems searching for bookmarks as it will be quite obvious where a specific resource could be. Admittedly, it does require some maintenance to keep a large bookmark collection in Notion, but it pays off soon and the pay-off accumulates over time.&lt;/p&gt;
&lt;h1 id=&#34;organizing-self-written-resources&#34;&gt;Organizing Self-Written Resources&lt;/h1&gt;
&lt;p&gt;Having a proper system for external resources is great, but it does not help much if everything you write yourself is a mess. Therefore, it is essential to organize self-written resources.&lt;/p&gt;
&lt;h2 id=&#34;blog-posts&#34;&gt;Blog Posts&lt;/h2&gt;
&lt;p&gt;My most successful blog post is about &lt;a href=&#34;https://tkainrad.dev/posts/using-hugo-gitlab-pages-and-cloudflare-to-create-and-run-this-website/&#34;&gt;how I run and host this website for free&lt;/a&gt;. However, the post does not cover the most work-intensive part about running this site: Writing and maintaining blog posts. For this, I heavily rely on Notion. I have a database with all my past blog posts and ideas for future posts. It is simple to add tags like &lt;code&gt;done&lt;/code&gt;, &lt;code&gt;doing&lt;/code&gt;, &lt;code&gt;idea&lt;/code&gt;, and to view the database as a kanban board with lanes based on those tags.&lt;/p&gt;
&lt;h3 id=&#34;drafting-posts&#34;&gt;Drafting Posts&lt;/h3&gt;
&lt;p&gt;All posts that are published on this blog have been drafted in &lt;a href=&#34;https://www.notion.so/?r=cb45b9cefd824015a044b3336009be32&#34;&gt;Notion&lt;/a&gt;. When they are more or less finished, I use the markdown export and copy the content into a new text file in my &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt; project. There are some minor issues with this. Usually, I have to adjust code formattings and links, but overall it works very well and has some nice benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Edit on all devices&lt;/strong&gt;&lt;br&gt;
Using notion, I can easily edit my drafts on all devices, even on my phone when I am moving.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use existing notion content&lt;/strong&gt;&lt;br&gt;
I can quickly add code snippets, links, and other stuff that I keep in Notion. This can be done via database relations or just by dragging the respective blocks into the draft page.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lightweight version control&lt;/strong&gt;&lt;br&gt;
As developers, we love GIT. However, for adding a couple of lines to a blog post, it is an overkill. Notion has versioning features build-in and keeps track of all changes automatically. Naturally, it does not offer sophisticated merging options, but it&amp;rsquo;s easy to stay clear of conflicts when you are writing a personal blog.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once a post is released, I move the post from &lt;code&gt;doing&lt;/code&gt; to &lt;code&gt;released&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;keeping-track-of-update-ideas&#34;&gt;Keeping Track of Update Ideas&lt;/h3&gt;
&lt;p&gt;You may have noticed that some of my blog posts are quite long. Keeping them up-to-date is a little challenging. Fortunately, I have Notion to assist me with this. I have a database where I add proposals for updating my existing posts. Using a relation, I link these ideas to the respective posts in my posts database:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/managing-my-personal-knowledge-base/notion-blog-update-proposals.png&#34;
         alt=&#34;It is hard to know when you are finished setting up the perfect Linux workstation.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;It is hard to know when you are finished &lt;a href=&#34;https://tkainrad.dev/posts/setting-up-linux-workstation/&#34;&gt;setting up the perfect Linux workstation&lt;/a&gt;.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;tracking-sources&#34;&gt;Tracking Sources&lt;/h3&gt;
&lt;p&gt;To keep track of third-party resources that were helpful in creating a post, I add them to my bookmarks database and link them to my posts database.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/managing-my-personal-knowledge-base/notion-blog-sources.png&#34;
         alt=&#34;My bookmarks database filtered to show only entries related to this post.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;My bookmarks database filtered to show only entries related to this post.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;You can also have a look at the full list of sources for this post via a &lt;a href=&#34;https://www.notion.so/tkpersonal/047517457f70471a91a9dac793b8d51b?v=802c2ec373fa43d0b7caf16ed8bfccda&#34;&gt;public fraction of my bookmarks database&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;taking-notes&#34;&gt;Taking Notes&lt;/h2&gt;
&lt;p&gt;I suppose every software engineer takes notes to some extent, even if they do not have a system and just casually write stuff into text files.
There is also a large amount of free and paid software for taking and organizing notes. Some people even like to use physical paper with the &lt;a href=&#34;https://en.wikipedia.org/wiki/Bullet_Journal&#34;&gt;BuJo&lt;/a&gt; system.&lt;/p&gt;
&lt;p&gt;No matter which method you prefer, you have to think about when, why, and how to take notes.&lt;/p&gt;
&lt;h3 id=&#34;when-to-take-notes&#34;&gt;When to take Notes&lt;/h3&gt;
&lt;p&gt;I thought quite a bit about this question, and previously I did not adhere to any predefined rules regarding this matter. To gain a better understanding of my habits and to decide what worked best in the past, I looked through my notes from the last years that were spread over multiple applications and lots of files. I tried to find common patterns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Capturing information from audio sources&lt;/strong&gt;&lt;br&gt;
I am a visual learner and prefer written content over audio. However, there are many types of audio sources that everybody encounters:
&lt;ul&gt;
&lt;li&gt;Meetings&lt;/li&gt;
&lt;li&gt;Presentations&lt;/li&gt;
&lt;li&gt;Meetups&lt;/li&gt;
&lt;li&gt;Conferences&lt;/li&gt;
&lt;li&gt;Informal Discussions&lt;/li&gt;
&lt;li&gt;Videos&lt;/li&gt;
&lt;li&gt;Podcasts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Preserving information that is often needed but doesn&amp;rsquo;t fit in any proper documentation project&lt;/strong&gt;&lt;br&gt;
In my case, these are things like the following:
&lt;ul&gt;
&lt;li&gt;Command-lines with multiple parameters, complex options, and paths. I keep them in my notes so that I can quickly copy-paste them in the potentially distant future.&lt;/li&gt;
&lt;li&gt;Instructions for tasks that will likely have to be repeated in the future, for example
&lt;ul&gt;
&lt;li&gt;setting up a development environment for a specific tech stack&lt;/li&gt;
&lt;li&gt;deploying a project on a specific hosting service.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Questions&lt;/strong&gt;&lt;br&gt;
Sometimes I think of a question that I would like to ask someone specific, who is not available at the moment. The question and its eventual answer are a nice use case for a note.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;Thanks to a more sophisticated command-line setup with Zsh and various plugins for auto-completion and history-search, preserving command-lines has become much less important for me. If you are still using Bash with its default &lt;code&gt;Ctl&lt;/code&gt; -  &lt;code&gt;r&lt;/code&gt; search, I think you would profit from my &lt;a href=&#34;https://tkainrad.dev/posts/setting-up-linux-workstation/&#34;&gt;post on setting up a Linux workstation&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;On the other hand, it is important to make clear also what &lt;strong&gt;should not be a note&lt;/strong&gt; in this system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project-specific knowledge&lt;/strong&gt;&lt;br&gt;
Everything that is directly related to a specific software project, should not be a note in your personal knowledge base. Instead, it should go into whichever system is used to keep track of issues, merge requests, documentation, and so on. This post includes a &lt;a href=&#34;#project-specific-knowledge&#34;&gt;separate section on this below&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Information that is needed for a very short time&lt;/strong&gt;
Sometimes I need to take notes during a conversation that I will need only immediately afterward. For this, I just open VSCode and type ahead. Once the information is no longer needed, the file can be deleted completely.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In general, don&amp;rsquo;t overdo and don&amp;rsquo;t underdo it. If you take notes all the time, you create overhead and redundancy. If you never take notes, well, you will not have notes.&lt;/p&gt;
&lt;h3 id=&#34;why-take-notes&#34;&gt;Why Take Notes&lt;/h3&gt;
&lt;p&gt;The obvious answer is to remember things. This does not mean that note-taking should replace your memory. To the contrary, taking notes provides structure and context and therefore helps your brain build up a map of your knowledge. I believe that taking notes &lt;em&gt;increases&lt;/em&gt; the amount of data that you can recall from memory.&lt;/p&gt;
&lt;p&gt;Additionally, some things are just very hard to remember, such as complex tech stacks presented at meetups, command-lines with several opaque options, numbers, and so on. Writing these things down can extend your knowledge base significantly. Notes are also very helpful when writing documentation in the future, or for writing blog posts like this one ;-)&lt;/p&gt;
&lt;h3 id=&#34;how-to-take-notes&#34;&gt;How to Take Notes&lt;/h3&gt;
&lt;p&gt;It can be tempting to just type ahead during a talk. Noting down whatever seems interesting at the moment. However, you should meet basic formal requirements and write down some context. Otherwise, you will have trouble extracting useful information from your notes in the future. If you take notes regularly, it is also important to organize them by assigning tags and properties, having a creation date, being able to filter and search through them, and so on. Software can help with these things.&lt;/p&gt;
&lt;p&gt;Until about a year ago I kept notes mainly on my local machines, syncing them with Dropbox. Applications that focus almost exclusively on note-taking, such as &lt;a href=&#34;https://evernote.com/&#34;&gt;Evernote&lt;/a&gt;, never had that much appeal to me, even though I used Google Keep/Notes. It is not so bad, supports e.g. labeling and works with little friction on mobile. However, there is not even basic formatting support, let alone markdown formatting or code syntax highlighting.&lt;/p&gt;
&lt;p&gt;Then, I discovered &lt;a href=&#34;https://www.notion.so/?r=cb45b9cefd824015a044b3336009be32&#34;&gt;Notion&lt;/a&gt;.
At first, It wasn&amp;rsquo;t really about note-taking for me. I liked it mainly because of its database concept and for organizing third-party resources as described in the &lt;a href=&#34;#bookmarking&#34;&gt;bookmarking section&lt;/a&gt;. However, by now, I use it heavily for almost all of my notes and other types of self-written content. I think Notion&amp;rsquo;s mix of markdown syntax support, slash commands, and WYSIWYG makes for a great writing experience.&lt;/p&gt;
&lt;p&gt;I strongly recommend organizing all your notes in a single database with appropriate tags, such as &lt;code&gt;meeting&lt;/code&gt;, &lt;code&gt;presentation&lt;/code&gt;, &lt;code&gt;tc&lt;/code&gt;, and &lt;code&gt;question&lt;/code&gt;. Similar to the bookmarks database described above, Notion will automatically provide a column with the creation time of the note. If you have frequent meetings with the same people, you can think about adding a &lt;em&gt;Participants&lt;/em&gt; column.&lt;/p&gt;
&lt;p&gt;A notes database is the perfect use case for another great Notion feature: &lt;a href=&#34;https://www.notion.so/Database-templates-454ed5ab5bd24226b58d176697bd7e10&#34;&gt;Templates&lt;/a&gt;. This feature will allow you to created templates for your different types of notes and then, for example, add a new meeting note with one click. Depending on the template, the new note comes with a pre-defined layout and might include fields for the meeting&amp;rsquo;s participants and agenda. This is what opens when I click to add new meeting notes in my database:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/managing-my-personal-knowledge-base/notion-meeting-notes.png&#34;
         alt=&#34;My basic meeting notes template.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;My basic meeting notes template.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h1 id=&#34;project-specific-knowledge&#34;&gt;Project-specific Knowledge&lt;/h1&gt;
&lt;p&gt;In my experience, it is best to keep project information as closely together as possible. For me, this means that issue tracking, merge requests (i.e. pull requests), documentation, and everything else that comes with a software project should live alongside the source code.&lt;/p&gt;
&lt;p&gt;This practice guarantees that anyone who works on the code now, or in the future, has access to all the information. My solution for this is to use GitLab for everything related to a specific project. This includes pretty much everything except some quick notes that I sometimes write in Notion because I do not want anyone else to see them.&lt;/p&gt;
&lt;p&gt;To do this, GitLab offers a lot of project management and documentation features, such as issue management, a project wiki, a Code snippet space for all projects and accounts, and more.
A project wiki is a great place for all documentation that is not specific to an issue. This can include the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Instructions for setting up the development environment&lt;/li&gt;
&lt;li&gt;Instructions for manual deployment&lt;/li&gt;
&lt;li&gt;Architecture descriptions and diagrams. I am a big fan of &lt;a href=&#34;https://mermaidjs.github.io/#/&#34;&gt;mermaid&lt;/a&gt;, which allows creating UML diagrams with Markdown and is supported by GitLab.&lt;/li&gt;
&lt;li&gt;Guidelines regarding naming conventions, design paradigms, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;If you have read my post on &lt;a href=&#34;https://tkainrad.dev/posts/using-hugo-gitlab-pages-and-cloudflare-to-create-and-run-this-website/&#34;&gt;running and hosting this website&lt;/a&gt;, you know that I use GitLab also for this personal blog project. Nevertheless, I use Notion for much of the project documentation and management tasks, because it comes with some unique benefits for this use case, as described &lt;a href=&#34;#blog-posts&#34;&gt;above&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;Of course, there are alternatives. GitHub has caught up in the past and offers a very similar feature set. I have only limited experience with Atlassian products, but I suppose you can achieve the same thing by using a mix of BitBucket, Confluence, and Jira.&lt;br&gt;
On a side note, writing in Confluence feels a lot like writing in Notion. Both recognize markdown syntax and offer slash commands to quickly insert complex content types.&lt;/p&gt;
&lt;h1 id=&#34;managing-files&#34;&gt;Managing Files&lt;/h1&gt;
&lt;p&gt;This one is relatively straightforward. I think the most important thing is to use some form of cloud storage. Personally, I use Dropbox.&lt;br&gt;
Next, you should think about a proper folder structure. Finally, you should be able to search for your files quickly.&lt;/p&gt;
&lt;p&gt;I use &lt;a href=&#34;https://tkainrad.dev/posts/setting-up-linux-workstation/&#34;&gt;Linux&lt;/a&gt; and rely on Albert for searching through files. If this doesn&amp;rsquo;t work I use &lt;a href=&#34;https://linuxize.com/post/locate-command-in-linux/&#34;&gt;the &lt;code&gt;locate&lt;/code&gt; command&lt;/a&gt; and, my last resort are command-line tools, such as &lt;code&gt;find&lt;/code&gt;, and &lt;code&gt;grep&lt;/code&gt;. They are still king for complex search requirements, such as regex matching and including also file content.&lt;/p&gt;
&lt;p&gt;If you can tick the three boxes&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;availability on all devices&lt;/li&gt;
&lt;li&gt;structure&lt;/li&gt;
&lt;li&gt;searchable&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;you should be all set.&lt;/p&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;Notion databases support file columns. This means you can upload a different file to each cell. I have not yet found a use case related directly to software engineering for this. However, it is great for managing invoices.&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;additional-use-cases&#34;&gt;Additional Use Cases&lt;/h1&gt;
&lt;p&gt;In this section, I want to present some special kinds of knowledge management use cases that are unique to software engineering. Most of them I have picked up properly only after starting to use &lt;a href=&#34;https://www.notion.so/?r=cb45b9cefd824015a044b3336009be32&#34;&gt;Notion&lt;/a&gt;. By no means, I want to say that you should copy all of them, but you are very welcome to try out what you think looks interesting.&lt;/p&gt;
&lt;h2 id=&#34;code-snippets&#34;&gt;Code Snippets&lt;/h2&gt;
&lt;p&gt;Currently, I have two ways of collecting code snippets. I hope I can eventually integrate them somehow.&lt;/p&gt;
&lt;h3 id=&#34;vscode-user-snippets&#34;&gt;VSCode User Snippets&lt;/h3&gt;
&lt;p&gt;The first one are &lt;a href=&#34;https://code.visualstudio.com/&#34;&gt;VSCode&lt;/a&gt; User Snippets. VSCode does many things right and snippet management is one of them. It is very easy to define new snippets and use them by typing a predefined string. Simply press &lt;code&gt;Ctrl&lt;/code&gt;-&lt;code&gt;Shift&lt;/code&gt;-&lt;code&gt;p&lt;/code&gt; and choose &lt;em&gt;Preferences: Configure User Snippets&lt;/em&gt;. Then, you can split your snippets into multiple files or just put all of them together in the &lt;code&gt;global-snipptes.code-snippets&lt;/code&gt; file. These are some of my snippets for writing blog posts with markdown and Hugo:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;21
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;22
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;23
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;24
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;25
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;26
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;27
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;28
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;figure&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;:&lt;/span&gt; {
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;scope&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;markdown&amp;#34;&lt;/span&gt;,
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;prefix&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;fig&amp;#34;&lt;/span&gt;,
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;body&amp;#34;&lt;/span&gt;: [
        &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{&amp;lt; figure src=\&amp;#34;/images/\&amp;#34;  class=\&amp;#34;center-figure\&amp;#34;  caption=\&amp;#34;\&amp;#34; &amp;gt;}}&amp;#34;&lt;/span&gt;
    ],
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;description&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;Insert hugo figure shortcode&amp;#34;&lt;/span&gt;
}&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;,&lt;/span&gt;
&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;callout&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;:&lt;/span&gt; {
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;scope&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;markdown&amp;#34;&lt;/span&gt;,
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;prefix&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;callout&amp;#34;&lt;/span&gt;,
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;body&amp;#34;&lt;/span&gt;: [
        &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{% callout \&amp;#34;warning info tip note\&amp;#34; %}}&amp;#34;&lt;/span&gt;,
        &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;,
        &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{%/callout %}}&amp;#34;&lt;/span&gt;
    ],
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;description&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;Insert hugo callout shortcode&amp;#34;&lt;/span&gt;
}&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;,&lt;/span&gt;
&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;:&lt;/span&gt; {
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;scope&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;markdown&amp;#34;&lt;/span&gt;,
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;prefix&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;code&amp;#34;&lt;/span&gt;,
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;body&amp;#34;&lt;/span&gt;: [
        &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{&amp;lt; highlight bash \&amp;#34;linenos=table\&amp;#34;&amp;gt;}}&amp;#34;&lt;/span&gt;,
        &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;,
        &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;{{&amp;lt; / highlight &amp;gt;}}&amp;#34;&lt;/span&gt;
    ],
    &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;description&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;Insert hugo code shortcode&amp;#34;&lt;/span&gt;
}&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The snippets can then be used by typing the prefix and pressing &lt;code&gt;Ctrl&lt;/code&gt;+&lt;code&gt;Space&lt;/code&gt;:
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/managing-my-personal-knowledge-base/vscode-insert-snippet.gif&#34;/&gt; 
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&#34;notion-snippet-database&#34;&gt;Notion Snippet database&lt;/h3&gt;
&lt;p&gt;The second use case concerns small pieces of code that I have written myself and that I would like to remember. This is not necessarily for productivity reasons, but rather for nostalgia. It is ideal for small algorithms, for example when you manage to substantially increase the performance of something by applying some clever technique.&lt;/p&gt;
&lt;p&gt;I keep those snippets in a Notion database, which works great because I can tag them and add arbitrary properties. A nice benefit of this habit is that it&amp;rsquo;s not a problem if you don&amp;rsquo;t do it for a while. Your database will not look messy all of a sudden.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/managing-my-personal-knowledge-base/notion-snippet.database.png&#34;
         alt=&#34;An exemplary entry of my code snippets database in Notion.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;An exemplary entry of my code snippets database in Notion.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;shortcut-database&#34;&gt;Shortcut Database&lt;/h2&gt;
&lt;p&gt;I believe that typing proficiency is important for a software engineer. It is easier to stay in flow if you can type fast and navigate around your IDE and tools with shortcuts. As my set of tools grew, I realized that I have a hard time to keep track of which shortcuts I am using, which of them work in which application, and whether I can change them so that they work across as many tools as possible.&lt;/p&gt;
&lt;p&gt;Therefore, I started to keep track of some shortcuts I would like to use everywhere. These are things like creating new tabs, creating new windows, opening the respective tool&amp;rsquo;s search feature and more.&lt;/p&gt;
&lt;p&gt;This database helps me identify overlaps and motivates me to think about which task could fit for a specific key-combination that I use often. For example, I use &lt;code&gt;Ctrl&lt;/code&gt; - &lt;code&gt;Shift&lt;/code&gt; - &lt;code&gt;T&lt;/code&gt; in IDEs to search for type definitions and  &lt;code&gt;Ctrl&lt;/code&gt; - &lt;code&gt;Shift&lt;/code&gt; - &lt;code&gt;R&lt;/code&gt; to search for resources in general. Since I have these key combinations memorized for years and can use it very quickly, I want to make use of it wherever possible. Therefore, I try to set up my applications so that some kind of search opens whenever I use one of these combinations. My Notion database makes it easy to see which applications offer search features and which of them I can modify.&lt;/p&gt;
&lt;p&gt;For example, my Browser now forwards &lt;code&gt;Ctrl&lt;/code&gt; - &lt;code&gt;Shift&lt;/code&gt; - &lt;code&gt;T&lt;/code&gt; / &lt;code&gt;R&lt;/code&gt; to &lt;a href=&#34;https://code.visualstudio.com/&#34;&gt;Workona&lt;/a&gt; and allows me to switch/search workspaces or use the general search, respectively.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/managing-my-personal-knowledge-base/shortcuts-database.png&#34;
         alt=&#34;Screenshot of my Notion shortcut database&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Screenshot of my Notion shortcut database&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Notion&amp;rsquo;s filtering and search capabilities are great for identifying problems with your setup. For me, a problem is when two applications use different shortcuts for a very similar operation. Unfortunately, Notion is negative example of an application that doesn&amp;rsquo;t let you customize its shortcuts and therefore reduces the usefulness of my database. If someone from the Notion team reads this at some point, I kindly ask you to lobby on my behalf to prioritize personalized keyboard shortcuts.&lt;/p&gt;
&lt;p&gt;I am excited about this use case and will talk about it in much more detail in a future blog post.&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;As of June 3rd, 2020, there is still no dedicated blog post for this. However, there is something more valuable:&lt;br&gt;
This post gave me the idea for &lt;a href=&#34;https://keycombiner.com&#34;&gt;https://keycombiner.com&lt;/a&gt;.&lt;br&gt;
It is a web application to track the keyboard shortcuts you use, get better at using them, and to learn new ones.
It is in open beta and I would love to hear your feedback!&lt;/p&gt;

&lt;/div&gt;
&lt;h2 id=&#34;tools-database&#34;&gt;Tools Database&lt;/h2&gt;
&lt;p&gt;I also have a Notion database to keep track of small web tools that I use. This serves little purpose for things that are used frequently as you will remember them anyway. However, for minor things that are only needed once in a while, such as color palette generators, detecting handwriting as LaTeX symbols, and checking SSL certificates this is quite handy. Usually, you can find these tools via google, but sometimes there are many tools for a task and it is nice to keep track of the ones that work best.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/managing-my-personal-knowledge-base/tools-database.png&#34;
         alt=&#34;Screenshot of my software tools database in Notion.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Screenshot of my software tools database in Notion.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Again, the important thing here is to not overdo it. There are extensive publicly available lists of such tools. Your list should not be a copy of those but rather a small collection of the things you like and need from time to time.&lt;/p&gt;
&lt;h2 id=&#34;collecting-ideas-for-new-projects&#34;&gt;Collecting Ideas for new Projects&lt;/h2&gt;
&lt;p&gt;Like many software engineers in these golden times, I sometimes think about starting new software projects, maybe even bootstrapping a small business.&lt;br&gt;
You probably guessed it already, I keep ideas for possible projects in a Notion database. The main benefit is, once again, that it allows me to easily link it with other databases, such as my bookmarks.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Similar posts often conclude with warnings that you should be very careful not to spend too much time on organizing and maintaining your knowledge and workflows. In principle, I agree with this sentiment. After all, you want to &lt;em&gt;increase&lt;/em&gt; your productivity.&lt;/p&gt;
&lt;p&gt;I do believe, however, that it is worth it to spend some time thinking about your knowledge management &lt;em&gt;system&lt;/em&gt;. The most important part about conceptualizing a system is to decide exactly which types of information you want to maintain in your knowledge base. If you get this right you will benefit for the rest of your career. Even if the tools might change in the future, the system will stay.&lt;/p&gt;
&lt;p&gt;Please don&amp;rsquo;t hesitate to share your own experiences in the comments below. I am sure that my workflows are not perfect, but they might get closer with your tips.&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;This post is released together with &lt;a href=&#34;https://tkainrad.dev/posts/reading-list-organizing-knowledge/&#34;&gt;a new post in my &lt;em&gt;Reading List&lt;/em&gt; series&lt;/a&gt;, which lists related resources. If you want to dive even deeper into knowledge management concepts, you might want to have a look.&lt;/p&gt;

&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Reading List (Organizing Knowledge)</title>
      <link>https://tkainrad.dev/posts/reading-list-organizing-knowledge/</link>
      <pubDate>Sun, 05 Jan 2020 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/reading-list-organizing-knowledge/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Knowledge management is an important topic for a software engineer. I have always been interested in material on the topic. When I decided to write an extensive post &lt;a href=&#34;https://tkainrad.dev/posts/managing-my-personal-knowledge-base/&#34;&gt;on my system and workflows&lt;/a&gt;, I started to collect sources and similar articles. To avoid further blowing up the original post, I will cover these sources in this separate post.&lt;/p&gt;
&lt;p&gt;There is a vast amount of resources covering specific tools and usage examples. However, I was a little surprised to see that there are relatively few resources elaborating on a complete software engineering knowledge management system.
Still, I found a couple of very interesting sources.&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;All resources linked in this post are also listed in &lt;a href=&#34;https://www.notion.so/tkpersonal/047517457f70471a91a9dac793b8d51b?v=802c2ec373fa43d0b7caf16ed8bfccda&#34;&gt;this public Notion database&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;hacker-news-discussions&#34;&gt;Hacker News Discussions&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; &lt;em&gt;Ask HN:&lt;/em&gt; posts on &lt;a href=&#34;https://news.ycombinator.com/&#34;&gt;Hacker News&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; Various authors&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; Various dates&lt;br&gt;
&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://news.ycombinator.com/item?id=21642289&#34;&gt;Ask HN: How do you organize document digests / personal knowledge? &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://news.ycombinator.com/item?id=21644216&#34;&gt;Ask HN: How do you manage/share domain knowledge in your company?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://news.ycombinator.com/item?id=21810400&#34;&gt;Ask HN: How do you keep your notes organized?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://news.ycombinator.com/item?id=21902130&#34;&gt;Tell HN: 500 unread mails, 2K unread articles, 5K unread posts – I am drowning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://news.ycombinator.com/item?id=21906650&#34;&gt;Ask HN: Best solutions for keeping a personal log?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hacker News gets quite a bit of hate for its discussions and comments. While it is true that people can be a little ignorant, I think it is one of the better places to have a civil discussion online.
The linked posts give a nice overview of what software engineers are looking for in the field of note-taking and knowledge management.&lt;/p&gt;
&lt;h1 id=&#34;gitlab-handbook&#34;&gt;GitLab Handbook&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; Company Handbook&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href=&#34;http://www.lauraelizabeth.co/&#34;&gt;GitLab&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; Frequently updated&lt;br&gt;
&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href=&#34;https://about.gitlab.com/handbook/&#34;&gt;https://about.gitlab.com/handbook/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I guess everybody knows the GitLab Handbook by now. It is a public document documenting how GitLab is run as a company, and therefore can be seen as a knowledge base. GitLab has a lot of success with it and the concept now gets picked up by more and more companies.
It is actually another use case for Notion: &lt;a href=&#34;https://launch.blendle.com/&#34;&gt;Blendle&lt;/a&gt;&amp;rsquo;s employee handbook is hosted as a &lt;a href=&#34;https://www.notion.so/Blendle-s-Employee-Handbook-7692ffe24f07450785f093b94bbe1a09&#34;&gt;public Notion document&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&#34;how-i-organize-my-knowledge-as-a-software-engineer&#34;&gt;How I organize my knowledge as a Software Engineer&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; Post on &lt;a href=&#34;https://dev.to/&#34;&gt;DEV Community&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href=&#34;https://twitter.com/brunopaz88&#34;&gt;Bruno Paz&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; Jan 16, 2019&lt;br&gt;
&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href=&#34;https://dev.to/brpaz/how-do-i-organize-my-knowledge-as-a-software-engineer-4387&#34;&gt;https://dev.to/brpaz/how-do-i-organize-my-knowledge-as-a-software-engineer-4387&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This post is similar to &lt;a href=&#34;https://tkainrad.dev/posts/managing-my-personal-knowledge-base/&#34;&gt;my own approach of documenting my knowledge management workflows&lt;/a&gt;
An engineer describes his personal strategies for organizing software engineering knowledge. I think the post is excellent and it gave me some ideas.&lt;/p&gt;
&lt;h1 id=&#34;how-ive-been-using-notion-personally-and-professionally&#34;&gt;How I&amp;rsquo;ve Been Using Notion Personally and Professionally&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; Post on &lt;a href=&#34;https://css-tricks.com/&#34;&gt;CSS-Tricks&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href=&#34;https://twitter.com/chriscoyier&#34;&gt;Chris Coyier&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; Jan 16, 2019&lt;br&gt;
&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href=&#34;https://css-tricks.com/how-ive-been-using-notion-personally-and-professionally/&#34;&gt;https://css-tricks.com/how-ive-been-using-notion-personally-and-professionally/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is an extensive post from the co-found of &lt;a href=&#34;https://codepen.io/&#34;&gt;CodePen&lt;/a&gt; about how he uses Notion for all kinds of things.&lt;/p&gt;
&lt;h1 id=&#34;content-by-the-creator-of-dnote&#34;&gt;Content by the creator of Dnote&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; blog posts and company website&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href=&#34;https://sungwoncho.com/about/&#34;&gt;Sung Cho&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; Feb 5, 2018 &amp;amp; Sep 9, 2018&lt;br&gt;
&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.getdnote.com/blog/writing-everything-i-learn-coding-for-a-month/&#34;&gt;I Wrote Down Everything I Learned While Programming for a Month&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.getdnote.com/blog/how-i-built-personal-knowledge-base-for-myself/&#34;&gt;How I Built a Personal Knowledge Base for Myself&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.getdnote.com/&#34;&gt;Dnote: A Simple Personal Knowledge Base &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://sungwoncho.com/about/&#34;&gt;Sung Cho&lt;/a&gt; is someone who has thought even more about personal knowledge management than me. In his articles, he describes the usual problems, such as organization overhead, and limited searchability. Because of these issues, he decided to build his own solution that takes a radical approach to the issue. As outlined in my own post, I think that a fundamental part of a knowledge base is to incorporate existing public web resources, which is something that &lt;a href=&#34;https://www.getdnote.com/&#34;&gt;Dnote&lt;/a&gt; does not seem to address. Still, I learned a lot from his reasoning and Dnote is an interesting tool, possibly in addition to some of the tools I have presented.&lt;/p&gt;
&lt;h1 id=&#34;geistmap-a-personal-knowledge-base-with-a-focus-on-connections&#34;&gt;GeistMap: A personal knowledge base with a focus on connections&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; Post on &lt;a href=&#34;https://hackernoon.com/&#34;&gt;Hacker Noon&lt;/a&gt; and GitHub repository&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; Bryan Haakman
&lt;strong&gt;Date:&lt;/strong&gt; Jan 16, 2019&lt;br&gt;
&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://hackernoon.com/building-a-open-source-personal-knowledge-base-45c25f5a4324&#34;&gt;Building an open source personal knowledge base&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/bryanph/GeistMap&#34;&gt;https://github.com/bryanph/GeistMap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Similar to the posts above, &lt;a href=&#34;https://twitter.com/chriscoyier&#34;&gt;Chris Coyier&lt;/a&gt; has created his own knowledge base tool. His focus lies on visualizing connections between notes and information. The tool is open source and published on &lt;a href=&#34;https://github.com/bryanph/GeistMap&#34;&gt;https://github.com/bryanph/GeistMap&lt;/a&gt;. His blog post defines excellent criteria that any such software should adhere to.&lt;/p&gt;
&lt;h1 id=&#34;my-text-data-public-text-file-repository&#34;&gt;My Text Data: Public text file repository&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; GitLab repository&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href=&#34;http://www.tastyfish.cz/&#34;&gt;Miloslav Číž&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; Frequent updates&lt;br&gt;
&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href=&#34;https://gitlab.com/drummyfish/my_text_data&#34;&gt;https://gitlab.com/drummyfish/my_text_data&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is a unique take on the personal knowledge base concept, a public GIT repository with text files about all kinds of topics. I do not think that it&amp;rsquo;s a good idea to share this kind of personal data publicly, but it is interesting to see someone doing it.&lt;/p&gt;
&lt;h1 id=&#34;keep-productive-blog&#34;&gt;Keep Productive Blog&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; Blog and Youtube Channel&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; Mainly &lt;a href=&#34;https://twitter.com/FrancescoD_Ales&#34;&gt;Francesco D&amp;rsquo;Alessio&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; Frequent updates&lt;br&gt;
&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.keepproductive.com/blog&#34;&gt;Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/user/cesidalessio&#34;&gt;Youtube Channel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/FrancescoD_Ales&#34;&gt;Francesco D&amp;rsquo;Alessio&lt;/a&gt; is a professional content creator focusing on productivity software. He often interviews guests about their Notion usage, which is a nice way for me to encounter fresh ideas.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I hope you found something that is of interest to you. If you would like to point out other resources covering knowledge management, you are very welcome to share them in the comments below or privately via &lt;a href=&#34;mailto:thomas@tkainrad.dev&#34;&gt;email&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>People to follow on Twitter who actually tweet about Software Engineering</title>
      <link>https://tkainrad.dev/posts/twitter-accounts-in-software-engineering/</link>
      <pubDate>Sun, 03 Nov 2019 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/twitter-accounts-in-software-engineering/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;I admit it, the topic of this post is not very original. If you google for its title, you will find countless lists, some of them by large publications. I should know, because I tried to incorporate recommendations from these resources multiple times throughout my 8-year long Twitter journey. However, it always turned out that many of the recommendations are of little use for me due to one or multiple of the following reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The recommended person is famous, but tweets very rarely or not at all.&lt;/li&gt;
&lt;li&gt;The person is famous, but does not tweet about software engineering.&lt;/li&gt;
&lt;li&gt;The person is CEO of a software company and their tweets are primarily advertisements or announcements related to the company.&lt;/li&gt;
&lt;li&gt;The person tweets the same things as everyone else. In principle, there is nothing wrong with that, but if you follow a hundred people who tweet variations of the same 240 characters take, it can become a bit dull.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To avoid these pitfalls, I define the following criteria:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Must tweet somewhat regularly, at the very least more than once a month.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Must actually tweet about software engineering or at least closely related topics, such as the software startup scene.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I (subjectively) find the tweets interesting.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://imgs.xkcd.com/comics/twitter_verification.png&#34;
         alt=&#34;With my list, you don&amp;amp;rsquo;t have to rely on Twitter&amp;amp;rsquo;s verification system (Source: xkcd.com)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;With my list, you don&amp;rsquo;t have to rely on Twitter&amp;rsquo;s verification system (Source: xkcd.com)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Obviously, any such list is highly opinionated and will always represent only a very small subset of all the amazing twitter accounts out there. Nevertheless, I am confident that my list contains many accounts that most people working in software engineering will find interesting.&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;I try to fit the people into different categories for better readability. Please note that some people could easily fit into multiple categories.&lt;/p&gt;

&lt;/div&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;The listed information is taken directly from the respective person&amp;rsquo;s Twitter account. Only if their description does not give information about their role, I give an additional line stating their position in &lt;em&gt;italics&lt;/em&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;I created the first version of this post manually, but as the list grew, this was no longer feasible. So I googled for a suitable &lt;a href=&#34;https://github.com/bear/python-twitter&#34;&gt;Twitter API library&lt;/a&gt; and wrote a few lines of hacky Python code, that can generate the required Markdown:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;twitter&lt;/span&gt;
&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#0cf;font-weight:bold&#34;&gt;re&lt;/span&gt;

&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# You have to apply for a developer account to get API credentials&lt;/span&gt;
api &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; twitter&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;Api(consumer_key&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;,
                  consumer_secret&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;,
                  access_token_key&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;,
                  access_token_secret&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;)

&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# In practice, the code below is in a loop&lt;/span&gt;
user &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; api&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;GetUser(screen_name&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;current_screen_name, return_json&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;True)

screen_name &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; user[&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;screen_name&amp;#39;&lt;/span&gt;]
name &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; user[&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;]
description &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; re&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;sub(&lt;span style=&#34;color:#c30&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;\n+&amp;#39;&lt;/span&gt;,  &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39; - &amp;#39;&lt;/span&gt;, user[&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;description&amp;#39;&lt;/span&gt;])
&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# convert twitter handles to Markdown links&lt;/span&gt;
description &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; re&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;sub(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;(@[\w\d]+)&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;[&lt;/span&gt;&lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\\&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;1](https://twitter.com/&lt;/span&gt;&lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\\&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;1)&amp;#39;&lt;/span&gt;, description)
description &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; description&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;replace(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;twitter.com/@&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;twitter.com/&amp;#34;&lt;/span&gt;)
location &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; user[&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;location&amp;#39;&lt;/span&gt;]

&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;[**&lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt; @&lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;**](https://twitter.com/&lt;/span&gt;&lt;span style=&#34;color:#a00&#34;&gt;%s&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;)  &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;%&lt;/span&gt;(name,screen_name,screen_name))
&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; description:
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;print&lt;/span&gt;(description &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;  &amp;#34;&lt;/span&gt;)
&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; location:
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;print&lt;/span&gt;(location &lt;span style=&#34;color:#555&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;  &amp;#34;&lt;/span&gt;)
&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;url&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#000;font-weight:bold&#34;&gt;in&lt;/span&gt; user[&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;entities&amp;#39;&lt;/span&gt;]):
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;print&lt;/span&gt;(user[&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;entities&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;url&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;urls&amp;#39;&lt;/span&gt;][&lt;span style=&#34;color:#f60&#34;&gt;0&lt;/span&gt;][&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;expanded_url&amp;#39;&lt;/span&gt;])
&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;---&lt;/span&gt;&lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id=&#34;software-engineers&#34;&gt;Software Engineers&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/bagder&#34;&gt;&lt;strong&gt;Daniel Stenberg @bagder&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Internet protocol geek. I write curl. I play with HTTP and QUIC within IETF. On team @wolfSSL. HTTP/3 is coming. I don&amp;rsquo;t know anything.
Stockholm Sweden&lt;br&gt;
&lt;a href=&#34;https://daniel.haxx.se&#34;&gt;https://daniel.haxx.se&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/dan_abramov&#34;&gt;&lt;strong&gt;Dan Abramov @dan_abramov&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
he/him&lt;br&gt;
&lt;a href=&#34;https://overreacted.io&#34;&gt;https://overreacted.io&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Co-author of Redux and Create React App.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/mitsuhiko&#34;&gt;&lt;strong&gt;Armin Ronacher @mitsuhiko&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Creator of Flask; Building stuff at @getsentry — writing and talking about system arch, API design and lots of Python / Rust. &lt;a href=&#34;https://www.twitch.tv/themitsuhiko&#34;&gt;https://www.twitch.tv/themitsuhiko&lt;/a&gt;&lt;br&gt;
Vienna, Austria&lt;br&gt;
&lt;a href=&#34;https://lucumr.pocoo.org&#34;&gt;https://lucumr.pocoo.org&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/Nick_Craver&#34;&gt;&lt;strong&gt;Nick Craver @Nick_Craver&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Dad, Architecture Lead, Developer, Site Reliability Engineer &amp;amp; DBA. @StackOverflow. MS MVP. I build very fast things to improve life for millions of developers.&lt;br&gt;
North Carolina&lt;br&gt;
&lt;a href=&#34;https://nickcraver.com&#34;&gt;https://nickcraver.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/shanselman&#34;&gt;&lt;strong&gt;Scott Hanselman @shanselman&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Code, Race, OSS, Dad, Open Web, Black Hair, STEM, Beyoncé, @TheOfficialACM, MSFT, #T1D. Umuntu ngumuntu ngabantu @Hanselminutes podcast=inclusive tech talk!&lt;br&gt;
Portland, Oregon&lt;br&gt;
&lt;a href=&#34;https://hanselman.com&#34;&gt;https://hanselman.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/davidfowl?s=08&#34;&gt;&lt;strong&gt;David Fowler @davidfowl&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Partner Software Architect at Microsoft on the http://ASP.NET  team, Creator of SignalR, Barbadian, Tennis Player, Father, and Husband of @symonefowler&lt;br&gt;
Redmond&lt;br&gt;
&lt;a href=&#34;https://davidfowl.com&#34;&gt;https://davidfowl.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/aChrisSmith&#34;&gt;&lt;strong&gt;Chris Smith @aChrisSmith&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Author of Programming F# and all-around nice guy. Employee #3 at &lt;a href=&#34;https://twitter.com/PulumiCorp&#34;&gt;@PulumiCorp&lt;/a&gt;. Ex-Googler, Microsoftie too, in case that matters. Opinions are my own, obvs.&lt;br&gt;
Seattle, WA&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/captainsafia&#34;&gt;&lt;strong&gt;Safia Abdalla @captainsafia&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
I help maintain the &lt;a href=&#34;https://twitter.com/nteractio&#34;&gt;@nteractio&lt;/a&gt; ecosystem, make open source software at &lt;a href=&#34;https://twitter.com/microsoft&#34;&gt;@microsoft&lt;/a&gt;, and write books and blogs. Dream big and follow through even bigger.&lt;br&gt;
Seattle, WA&lt;br&gt;
&lt;a href=&#34;https://safia.rocks&#34;&gt;https://safia.rocks&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/webology&#34;&gt;&lt;strong&gt;Jeff Triplett @webology&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Python, Django, Docker, Engineer, Partner at &lt;a href=&#34;https://twitter.com/revsys&#34;&gt;@revsys&lt;/a&gt; - Affiliations &lt;a href=&#34;https://twitter.com/ThePSF&#34;&gt;@ThePSF&lt;/a&gt; &lt;a href=&#34;https://twitter.com/defnado&#34;&gt;@defnado&lt;/a&gt; &lt;a href=&#34;https://twitter.com/djangocon&#34;&gt;@djangocon&lt;/a&gt; &lt;a href=&#34;https://twitter.com/djangoproject&#34;&gt;@djangoproject&lt;/a&gt; - Interests 🏀 ✨ 💪 🏃‍♂️ 🤖 (he/him)&lt;br&gt;
Lawrence, KS&lt;br&gt;
&lt;a href=&#34;https://jefftriplett.com&#34;&gt;https://jefftriplett.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/sliminality&#34;&gt;&lt;strong&gt;Sarah Lim @sliminality&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
I want to talk to you about the affect and aesthetics of computing.&lt;br&gt;
she/her/她&lt;br&gt;
&lt;a href=&#34;http://slim.computer&#34;&gt;http://slim.computer&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/mxstbr&#34;&gt;&lt;strong&gt;Max Stoiber @mxstbr&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Maker https://t.co/lDhfGoK9Ke https://t.co/86uNp5ZSzr Δ - Eng &lt;a href=&#34;https://twitter.com/gatsbyjs&#34;&gt;@gatsbyjs&lt;/a&gt;, prev &lt;a href=&#34;https://twitter.com/github&#34;&gt;@github&lt;/a&gt; (acq &lt;a href=&#34;https://twitter.com/withspectrum&#34;&gt;@withspectrum&lt;/a&gt;) 💼 - Created styled-components, react-boilerplate… 💅 - https://t.co/565oxi5nr2&lt;br&gt;
Wien, Österreich&lt;br&gt;
&lt;a href=&#34;https://mxstbr.com&#34;&gt;https://mxstbr.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/LostInTangent&#34;&gt;&lt;strong&gt;Jonathan Carter @LostInTangent&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
I build dev tools and services @ Microsoft (Codespaces, Live Share, IntelliCode) and maintain some OSS VS Code extensions (GistPad, CodeTour, GitDoc)&lt;br&gt;
Ballard, Seattle, WA&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/slightlylate&#34;&gt;&lt;strong&gt;Alex Russell @slightlylate&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Chrome Project 🐡 &amp;amp; Web Standards TL; Blink API OWNER - Named PWAs w/ &lt;a href=&#34;https://twitter.com/phae&#34;&gt;@phae&lt;/a&gt;; probably making her ☕ - Tweets are my own and do not represent my employer&amp;rsquo;s views.&lt;br&gt;
San Francisco, The Internet&lt;br&gt;
&lt;a href=&#34;https://infrequently.org/&#34;&gt;https://infrequently.org/&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/NatalieMarleny&#34;&gt;&lt;strong&gt;Natalie Marleny @NatalieMarleny&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
✏️ Application Engineer (Independent)&lt;br&gt;
London, England&lt;br&gt;
&lt;a href=&#34;http://github.com/nataliemarleny&#34;&gt;http://github.com/nataliemarleny&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&#34;indie-hackers&#34;&gt;Indie Hackers&lt;/h1&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;The people in this section are mostly software engineers as well, and many could also be described as founders. However, I feel like the theme of their tweets is quite different from those other categories and deserving of its own section.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/monicalent&#34;&gt;&lt;strong&gt;Monica Lent @monicalent&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Latin major turned software engineer 💖 JS, FP, U2F, Linux, and embarrassing myself in German on a daily basis. Building @affilimateio 👩🏻‍🚀&lt;br&gt;
Berlin, Germany&lt;br&gt;
&lt;a href=&#34;https://monicalent.com&#34;&gt;https://monicalent.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/nikitavoloboev&#34;&gt;&lt;strong&gt;Nikita Voloboev @nikitavoloboev&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Make &lt;a href=&#34;https://twitter.com/LearnAnything_&#34;&gt;@LearnAnything_&lt;/a&gt;. Love animals. Vegan. &lt;a href=&#34;http://github.com/nikitavoloboev&#34;&gt;http://github.com/nikitavoloboev&lt;/a&gt; &lt;a href=&#34;http://instagram.com/nikitavoloboev&#34;&gt;http://instagram.com/nikitavoloboev&lt;/a&gt;&lt;br&gt;
London&lt;br&gt;
&lt;a href=&#34;https://nikitavoloboev.xyz&#34;&gt;https://nikitavoloboev.xyz&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/calebporzio&#34;&gt;&lt;strong&gt;Caleb Porzio @calebporzio&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Launching Soon: &lt;a href=&#34;http://MakeVSCodeAwesome.com&#34;&gt;http://MakeVSCodeAwesome.com&lt;/a&gt;. Co-Host of &lt;a href=&#34;https://twitter.com/noplanstomerge&#34;&gt;@noplanstomerge&lt;/a&gt; | Creator of &lt;a href=&#34;https://twitter.com/LaravelLivewire&#34;&gt;@LaravelLivewire&lt;/a&gt; &amp;amp; &lt;a href=&#34;https://twitter.com/Alpine_JS&#34;&gt;@Alpine_JS&lt;/a&gt;&lt;br&gt;
Buffalo, NY&lt;br&gt;
&lt;a href=&#34;https://calebporzio.com&#34;&gt;https://calebporzio.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/umaar&#34;&gt;&lt;strong&gt;Umar Hansa @umaar&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
• I tweet developer tips and tricks • Made &lt;a href=&#34;http://ModernDevtools.com&#34;&gt;http://ModernDevtools.com&lt;/a&gt; • &lt;a href=&#34;https://twitter.com/@GoogleDevExpert&#34;&gt;@GoogleDevExpert&lt;/a&gt; member • Developer tips &lt;a href=&#34;http://umaar.com/dev-tips/&#34;&gt;http://umaar.com/dev-tips/&lt;/a&gt; • Previously web developer &lt;a href=&#34;https://twitter.com/@shazam&#34;&gt;@shazam &lt;/a&gt;&lt;br&gt;
London&lt;br&gt;
&lt;a href=&#34;https://numaar.com&#34;&gt;https://numaar.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/@frankdilo&#34;&gt;&lt;strong&gt;Francesco Di Lorenzo @frankdilo&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Founder. Hacker. Building &lt;a href=&#34;https://twitter.com/mailbrew&#34;&gt;@mailbrew&lt;/a&gt; to fix how we stay informed online.&lt;br&gt;
Milan, Italy&lt;br&gt;
&lt;a href=&#34;https://mailbrew.com&#34;&gt;https://mailbrew.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/chrisachard&#34;&gt;&lt;strong&gt;Chris Achard @chrisachard&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Building &lt;a href=&#34;https://twitter.com/meetingplace_io&#34;&gt;@meetingplace_io&lt;/a&gt;. Teaching everything I know about: React, Rails, JS, Node, at https://t.co/F6LJmRvN4t; online instructor &lt;a href=&#34;https://twitter.com/eggheadio&#34;&gt;@eggheadio&lt;/a&gt;&lt;br&gt;
Carmel, IN&lt;br&gt;
&lt;a href=&#34;http://chrisachard.com&#34;&gt;http://chrisachard.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/dvassallo&#34;&gt;&lt;strong&gt;Daniel Vassallo @dvassallo&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Becoming self-employed is easy, staying so is hard. I tweet about how I&amp;rsquo;m doing it. Building https://t.co/CP5YrWTk2Y. Creator on https://t.co/Ua6JqF8zb7.&lt;br&gt;
Seattle, WA&lt;br&gt;
&lt;a href=&#34;https://danielvassallo.com&#34;&gt;https://danielvassallo.com&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&#34;infosec&#34;&gt;InfoSec&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/troyhunt&#34;&gt;&lt;strong&gt;Troy Hunt @troyhunt&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Pluralsight author. Microsoft Regional Director and MVP for Developer Security. Online security, technology and “The Cloud”. Creator of &lt;a href=&#34;https://twitter.com/haveibeenpwned&#34;&gt;@haveibeenpwned&lt;/a&gt;.&lt;br&gt;
Australia&lt;br&gt;
&lt;a href=&#34;https://troyhunt.com&#34;&gt;https://troyhunt.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/SwiftOnSecurity?s=08&#34;&gt;&lt;strong&gt;SwiftOnSecurity @SwiftOnSecurity&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Systems security, industrial safety, author &lt;a href=&#34;http://DecentSecurity.com&#34;&gt;http://DecentSecurity.com&lt;/a&gt;  + &lt;a href=&#34;http://GotPhish.com&#34;&gt;http://GotPhish.com&lt;/a&gt; , write SciFi, sysadmin, &amp;amp; use Oxford commas. they/them/tay&lt;br&gt;
Cyber, USA&lt;br&gt;
&lt;a href=&#34;https://decentsecurity.com&#34;&gt;https://decentsecurity.com&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Taylor Swift parody account tweeting about InfoSec topics&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/matthew_d_green&#34;&gt;&lt;strong&gt;Matthew Green @matthew_d_green&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
I teach cryptography at Johns Hopkins.&lt;br&gt;
Baltimore, MD&lt;br&gt;
&lt;a href=&#34;https://blog.cryptographyengineering.com&#34;&gt;https://blog.cryptographyengineering.com&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Professor for cryptography at Johns Hopkins&lt;/em&gt;&lt;/p&gt;

&lt;div class=&#34;notices question&#34;&gt;
    &lt;p&gt;Initially, I wanted to do a section with &lt;em&gt;Academics&lt;/em&gt;. However, it turned out that I do not actually follow a lot of university researchers. Dou you know of any that would fit the criteria given in the introduction?&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;blockchain&#34;&gt;Blockchain&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/VitalikButerin&#34;&gt;&lt;strong&gt;Vitalik Buterin @VitalikButerin&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
See &lt;a href=&#34;https://about.me/vitalik_buterin&#34;&gt;https://about.me/vitalik_buterin&lt;/a&gt;  Not giving away ETH. For inquiries about me advising your ICO, please email: &lt;a href=&#34;mailto:noreply@buterin.com&#34;&gt;noreply@buterin.com&lt;/a&gt;&lt;br&gt;
Earth&lt;br&gt;
&lt;a href=&#34;https://vitalik.ca&#34;&gt;https://vitalik.ca&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Co-founder of Ethereum&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/el33th4xor&#34;&gt;&lt;strong&gt;Emin Gün Sirer @el33th4xor&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
CEO @avalabsofficial, Prof @Cornell, co-director @initc3org, currently on leave from the last two&lt;br&gt;
Williamsburg, NY&lt;br&gt;
&lt;a href=&#34;https://hackingdistributed.com&#34;&gt;https://hackingdistributed.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/NeerajKA&#34;&gt;&lt;strong&gt;Neeraj K. Agrawal @NeerajKA&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
comms &lt;a href=&#34;https://twitter.com/coincenter&#34;&gt;@coincenter&lt;/a&gt;—the cryptocurrency policy think tank | &lt;a href=&#34;mailto:neeraj@coincenter.org&#34;&gt;neeraj@coincenter.org&lt;/a&gt; https://t.co/SASIdi6UWM&lt;br&gt;
Washington, DC&lt;br&gt;
&lt;a href=&#34;http://coincenter.org&#34;&gt;http://coincenter.org&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/starkness&#34;&gt;&lt;strong&gt;Elizabeth Stark 😷 @starkness&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
big fan of the internet. like building things. cofounder &lt;a href=&#34;https://twitter.com/lightning&#34;&gt;@lightning&lt;/a&gt; labs, fellow &lt;a href=&#34;https://twitter.com/coincenter&#34;&gt;@coincenter&lt;/a&gt;. taught &lt;a href=&#34;https://twitter.com/stanford&#34;&gt;@stanford&lt;/a&gt; + &lt;a href=&#34;https://twitter.com/yalelawtech&#34;&gt;@yalelawtech&lt;/a&gt;.&lt;br&gt;
brooklyn + sf&lt;/p&gt;
&lt;h1 id=&#34;startup-scene&#34;&gt;Startup Scene&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/paulg&#34;&gt;&lt;strong&gt;Paul Graham @paulg&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href=&#34;https://paulgraham.com&#34;&gt;https://paulgraham.com&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Co-founder of Y Combinator&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/garrytan&#34;&gt;&lt;strong&gt;Garry Tan @garrytan&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Managing Partner, @Initialized. Earliest investor in @coinbase @instacart @flexport &amp;amp; 100+ more—$36B in startup market cap so far. Forbes Midas List 2019 #21 🚀&lt;br&gt;
San Francisco, CA&lt;br&gt;
&lt;a href=&#34;https://youtube.com/garrytan&#34;&gt;https://youtube.com/garrytan&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/KateClarkTweets&#34;&gt;&lt;strong&gt;Kate Clark @KateClarkTweets&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Writing about venture capital &amp;amp; startups &lt;a href=&#34;https://twitter.com/theinformation&#34;&gt;@theinformation&lt;/a&gt; | Tips to &lt;a href=&#34;mailto:kate@theinformation.com&#34;&gt;kate@theinformation.com&lt;/a&gt; | Signal &amp;amp; Telegram: 415-409-9095
San Francisco&lt;br&gt;
&lt;a href=&#34;https://techcrunch.com/newsletter&#34;&gt;https://techcrunch.com/newsletter&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/awilkinson&#34;&gt;&lt;strong&gt;Andrew Wilkinson @awilkinson&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Co-founder of Tiny, owner of &lt;a href=&#34;https://twitter.com/Dribbble&#34;&gt;@Dribbble&lt;/a&gt;, &lt;a href=&#34;https://twitter.com/MetaLab&#34;&gt;@MetaLab&lt;/a&gt;, and many others. Buying, starting, and investing in wonderful internet businesses since 2007.&lt;br&gt;
Victoria, Canada&lt;br&gt;
&lt;a href=&#34;http://www.tinycapital.com&#34;&gt;http://www.tinycapital.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/shl&#34;&gt;&lt;strong&gt;Sahil Lavingia @shl&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Founder &lt;a href=&#34;https://twitter.com/gumroad&#34;&gt;@gumroad&lt;/a&gt;, impending investor looking for QPs 👀&lt;br&gt;
Portland, OR&lt;br&gt;
&lt;a href=&#34;http://sahillavingia.com&#34;&gt;http://sahillavingia.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/Suhail&#34;&gt;&lt;strong&gt;Suhail Doshi @Suhail&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
New: https://t.co/YYa4AbttbR, Founder https://t.co/EFNC6mZg4B - Pizzatarian, programmer, &amp;amp; music maker&lt;br&gt;
Remote&lt;br&gt;
&lt;a href=&#34;https://mightyapp.com&#34;&gt;https://mightyapp.com&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&#34;ceos-and-founders&#34;&gt;CEOs and Founders&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/zeeg&#34;&gt;&lt;strong&gt;David Cramer @zeeg&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
building @getsentry&lt;br&gt;
San Francisco&lt;br&gt;
&lt;a href=&#34;https://sentry.io&#34;&gt;https://sentry.io&lt;/a&gt;&lt;br&gt;
&lt;em&gt;CEO of Sentry&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/sytses&#34;&gt;&lt;strong&gt;Sid Sijbrandij @sytses&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Legal first name: Sytse. Co-founder &amp;amp; CEO of GitLab. I love innovative projects, accessible education, all remote work, new cities, and macro economics. he/him&lt;br&gt;
San Francisco, California&lt;br&gt;
&lt;a href=&#34;https://about.gitlab.com&#34;&gt;https://about.gitlab.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/sama&#34;&gt;&lt;strong&gt;Sam Altman @sama&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
ready player sama&lt;br&gt;
&lt;em&gt;Former president of Y Combinator and current CEO of OpenAI&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/collision&#34;&gt;&lt;strong&gt;John Collison @collision&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Co-founder of @stripe.&lt;br&gt;
San Francisco&lt;br&gt;
&lt;a href=&#34;https://johncollison.ie&#34;&gt;https://johncollison.ie&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/codinghorror&#34;&gt;&lt;strong&gt;Jeff Atwood @codinghorror&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Indoor enthusiast. Co-founder of &lt;a href=&#34;http://stackoverflow.com&#34;&gt;http://stackoverflow.com&lt;/a&gt;  and &lt;a href=&#34;http://discourse.org&#34;&gt;http://discourse.org&lt;/a&gt; . Abyss domain expert. Disclaimer: I have no idea what I&amp;rsquo;m talking about.&lt;br&gt;
Bay Area, CA&lt;br&gt;
&lt;a href=&#34;https://blog.codinghorror.com&#34;&gt;https://blog.codinghorror.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/jack&#34;&gt;&lt;strong&gt;Jack Dorsey @jack&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;CEO of Twitter&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/eastdakota&#34;&gt;&lt;strong&gt;Matthew Prince @eastdakota&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
A little bit geek, wonk, and nerd. Repeat entrepreneur, recovering lawyer, and former ski instructor. Co-founder &amp;amp; CEO of Cloudflare (NYSE: NET).&lt;br&gt;
San Francisco, CA&lt;br&gt;
&lt;a href=&#34;https://cloudflare.com&#34;&gt;https://cloudflare.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/dhh&#34;&gt;&lt;strong&gt;David Heinemeier Hansson @dhh&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Creator of Ruby on Rails, Founder &amp;amp; CTO at Basecamp, NYT best-selling author, and Le Mans 24h class-winning racing driver.&lt;br&gt;
&lt;a href=&#34;https://dhh.dk&#34;&gt;https://dhh.dk&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/tobi&#34;&gt;&lt;strong&gt;Tobi Lutke @tobi&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href=&#34;https://twitter.com/Shopify&#34;&gt;@Shopify&lt;/a&gt; CEO by day, Dad in the evening, hacker at night. - Rails alumni; Comprehensivist. I like video games. (All tweets auto delete after 1y)&lt;br&gt;
Canada&lt;br&gt;
&lt;a href=&#34;http://tobi.lutke.com&#34;&gt;http://tobi.lutke.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/Austen&#34;&gt;&lt;strong&gt;Austen Allred @Austen&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
CEO &lt;a href=&#34;https://twitter.com/LambdaSchool&#34;&gt;@LambdaSchool&lt;/a&gt;: A CS education with no tuition until you get a job. I have seen the “Memes” and I think they’re very good.&lt;br&gt;
The Oasis&lt;br&gt;
&lt;a href=&#34;http://austenallred.com&#34;&gt;http://austenallred.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/Conaw&#34;&gt;&lt;strong&gt;Conor White-Sullivan @Conaw&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Co-founder of &lt;a href=&#34;https://twitter.com/RoamResearch&#34;&gt;@RoamResearch&lt;/a&gt;. Believer in tools for thought.&lt;br&gt;
San Francisco, CA&lt;br&gt;
&lt;a href=&#34;http://roamresearch.com&#34;&gt;http://roamresearch.com&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&#34;other-industry-professionals&#34;&gt;Other Industry Professionals&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/GergelyOrosz&#34;&gt;&lt;strong&gt;Gergely Orosz @GergelyOrosz&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Writing &lt;a href=&#34;http://newsletter.pragmaticengineer.com&#34;&gt;http://newsletter.pragmaticengineer.com&lt;/a&gt; &amp;amp; &lt;a href=&#34;https://twitter.com/EngGuidebook&#34;&gt;@EngGuidebook&lt;/a&gt;. Advisor &lt;a href=&#34;https://twitter.com/mobile__dev&#34;&gt;@mobile__dev&lt;/a&gt;. Uber &amp;amp; Skype alumni. Jobs with great eng culture: &lt;a href=&#34;http://pragmaticurl.com/jobs&#34;&gt;http://pragmaticurl.com/jobs&lt;/a&gt;&lt;br&gt;
Amsterdam, The Netherlands&lt;br&gt;
&lt;a href=&#34;https://pragmaticengineer.com&#34;&gt;https://pragmaticengineer.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/__apf__&#34;&gt;&lt;strong&gt;Adrienne Porter Felt @&lt;strong&gt;apf&lt;/strong&gt;&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
I like writing silly Tweets, but that doesn&amp;rsquo;t pay so I also make @googlechrome. 🇺🇸🇨🇷 she/her
Mountain View, CA&lt;br&gt;
&lt;a href=&#34;https://adrienneporterfelt.com&#34;&gt;https://adrienneporterfelt.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/rickasaurus&#34;&gt;&lt;strong&gt;Richard Minerich @rickasaurus&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Unicorn Dad, Dinosaur Programmer, Fancy Types, Machine Learning, Physics. CTO at Safe Banking Systems (Part of Accuity). Tweets/Opinions are my own.&lt;br&gt;
Hoboken, NJ&lt;br&gt;
&lt;a href=&#34;https://richardminerich.com&#34;&gt;https://richardminerich.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/amandaksilver&#34;&gt;&lt;strong&gt;Amanda Silver @amandaksilver&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
CVP of Product for Developer Tools @ Microsoft. &lt;a href=&#34;https://twitter.com/code&#34;&gt;@code&lt;/a&gt;, &lt;a href=&#34;https://twitter.com/VisualStudio&#34;&gt;@VisualStudio&lt;/a&gt;, &lt;a href=&#34;https://twitter.com/TypeScript&#34;&gt;@TypeScript&lt;/a&gt;, &lt;a href=&#34;https://twitter.com/dotnet&#34;&gt;@dotnet&lt;/a&gt;, etc. Nerd of nerds. Momming hard. Opinions mine. She/her. 🌈 HIRING!&lt;br&gt;
Seattle, WA&lt;br&gt;
&lt;a href=&#34;http://www.visualstudio.com&#34;&gt;http://www.visualstudio.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;a href=&#34;https://twitter.com/Reza_Zadeh&#34;&gt;&lt;strong&gt;Reza Zadeh @Reza_Zadeh&lt;/strong&gt;&lt;/a&gt;&lt;br&gt;
Machine Learning &lt;a href=&#34;https://twitter.com/Matroid&#34;&gt;@Matroid&lt;/a&gt; &amp;amp; &lt;a href=&#34;https://twitter.com/Stanford&#34;&gt;@Stanford&lt;/a&gt;. Optimizing for Intelligence.&lt;br&gt;
Palo Alto, CA&lt;br&gt;
&lt;a href=&#34;http://reza-zadeh.com&#34;&gt;http://reza-zadeh.com&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I believe that Twitter is a solid tool to stay aware of current discussions and trends in software engineering.
Hopefully, this list helped you to improve your timeline. In case you are just starting with Twitter, consider following also people from other domains to create a more diversified Twitter experience.&lt;/p&gt;
&lt;p&gt;Of course, suggestions are very much welcome, especially if they fit the above criteria.&lt;/p&gt;
&lt;p&gt;Last but not least, you are welcome to &lt;a href=&#34;https://twitter.com/ThomasKainrad&#34;&gt;follow me&lt;/a&gt; on Twitter, too. I tweet about all things software engineering, and post about working on my side-project &lt;a href=&#34;https://keycombiner.com&#34;&gt;KeyCombiner&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Copy-paste ready commands to set up SGE, PBS/TORQUE, or SLURM clusters</title>
      <link>https://tkainrad.dev/posts/copy-paste-ready-instructions-to-set-up-1-node-clusters/</link>
      <pubDate>Fri, 18 Oct 2019 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/copy-paste-ready-instructions-to-set-up-1-node-clusters/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Local HPC clusters continue to play a vital role in scientific research. Many universities, research institutions, and companies continue to maintain on-site clusters despite the efforts of the big cloud providers to expand their offerings into this area.&lt;/p&gt;
&lt;p&gt;The software systems responsible for making these clusters of computers work together can be called &lt;em&gt;Distributed Research Management System.&lt;/em&gt; The most commonly used ones are SGE, PBS/TORQUE, and SLURM. Unfortunately, setting those up can be very painful. There is a lot of outdated information on the internet. It is required to fight with several different configuration files and minor issues can mean that no command will run properly.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/drms.svg&#34;
         alt=&#34;Operating principle of a traditional resource-manager based computing cluster.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Operating principle of a traditional resource-manager based computing cluster.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If your task is to set up a large system that will be used for many years and you have got several days to do this perfectly, this might not be a problem. However, if you just want to play around with the different systems, spending several hours until you can submit your first job is a very bleak outlook.&lt;/p&gt;
&lt;p&gt;Therefore, I present copy-paste-ready instructions for setting up SGE, PBS/TORQUE, or SLURM on a single machine, which will act as the master and a compute node at the same time. All guides are tested on Ubuntu 18.04, but should work with little modification on most recent Linux installations. These commands can also serve to create container images or other automated setup workflows.&lt;/p&gt;
&lt;p&gt;Naturally, caution is warranted when executing commands as a super user. If you apply the listed commands on a production system, please make sure you understand everything that is done.&lt;/p&gt;
&lt;h1 id=&#34;slurm&#34;&gt;Slurm&lt;/h1&gt;
&lt;p&gt;Trying to set up SLURM on my development machine in order to run some test suites that need to interface with SLURM cost me quite a bit of time. There are plenty of extensive tutorials available. In contrast to SGE and PBS, there is even an up-to-date &lt;a href=&#34;https://slurm.schedmd.com/quickstart_admin.html&#34;&gt;official documentation&lt;/a&gt;. However, those resources cover much more than is needed for a single-node cluster and it is easy to get lost in the many configuration options that you will not need at first.&lt;/p&gt;
&lt;p&gt;Fortunately, I found &lt;a href=&#34;https://ubuntuforums.org/showthread.php?t=2404746&#34;&gt;this thread&lt;/a&gt; in the Ubuntu forums, which is the basis for my SLURM instructions.&lt;/p&gt;
&lt;p&gt;Start by installing munge and slurm:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo apt install munge slurm-wlm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, create a new file at &lt;code&gt;/etc/slurm-llnl/slurm.conf&lt;/code&gt; with the following content:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;21
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;22
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;23
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;24
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;25
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;26
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;27
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;28
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;29
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-PacmanConf&#34; data-lang=&#34;PacmanConf&#34;&gt;&lt;span style=&#34;color:#309&#34;&gt;ControlMachine&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&amp;lt;YOUR-HOST-NAME&amp;gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;MpiDefault&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;none&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;ProctrackType&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;proctrack/pgid&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;ReturnToService&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;1&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;SlurmctldPidFile&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;/var/run/slurm-llnl/slurmctld.pid&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;SlurmdPidFile&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;/var/run/slurm-llnl/slurmd.pid&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;SlurmdSpoolDir&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;/var/lib/slurm-llnl/slurmd&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;SlurmUser&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;slurm&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;StateSaveLocation&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;/var/lib/slurm-llnl/slurmctld&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;SwitchType&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;switch/none&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;TaskPlugin&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;task/none&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# SCHEDULING&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;FastSchedule&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;1&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;SchedulerType&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;sched/builtin&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;SelectType&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;select/linear&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# LOGGING AND ACCOUNTING&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;AccountingStorageType&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;accounting_storage/none&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;ClusterName&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&amp;lt;YOUR-HOST-NAME&amp;gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;JobAcctGatherType&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;jobacct_gather/none&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;SlurmctldLogFile&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;/var/log/slurm-llnl/slurmctld.log&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;SlurmdLogFile&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;/var/log/slurm-llnl/slurmd.log&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# COMPUTE NODES&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;NodeName&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&amp;lt;YOUR-HOST-NAME&amp;gt; &lt;span style=&#34;color:#309&#34;&gt;CPUs&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;4 &lt;span style=&#34;color:#309&#34;&gt;Sockets&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;1 &lt;span style=&#34;color:#309&#34;&gt;CoresPerSocket&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;4 &lt;span style=&#34;color:#309&#34;&gt;ThreadsPerCore&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;2 &lt;span style=&#34;color:#309&#34;&gt;State&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;UNKNOWN&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;PartitionName&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;long &lt;span style=&#34;color:#309&#34;&gt;Nodes&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&amp;lt;YOUR-HOST-NAME&amp;gt; &lt;span style=&#34;color:#309&#34;&gt;Default&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;YES &lt;span style=&#34;color:#309&#34;&gt;MaxTime&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;INFINITE &lt;span style=&#34;color:#309&#34;&gt;State&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;UP&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You only need to replace &lt;code&gt;&amp;lt;YOUR-HOST-NAME&amp;gt;&lt;/code&gt; with your actual hostname. Simply execute the &lt;code&gt;hostname&lt;/code&gt; command if you are not sure what it is. Of course, you can also modify other settings, such as the number of CPUs, but the basic configuration from above should be enough for our 1-node cluster.&lt;/p&gt;
&lt;p&gt;We are almost finished, just enable and start the manager slurmctld:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo systemctl &lt;span style=&#34;color:#366&#34;&gt;enable&lt;/span&gt; slurmctld
sudo systemctl start slurmctld
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, enable and start the agent &lt;code&gt;slurmd&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;sudo systemctl &lt;span style=&#34;color:#366&#34;&gt;enable&lt;/span&gt; slurmd
sudo systemctl start slurmd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Congratulations, your Slurm system should be up an running! Use &lt;code&gt;sinfo&lt;/code&gt; to check the status of the manager and the agent. The command &lt;code&gt;scontrol show node&lt;/code&gt; will give you information about your node setup.&lt;/p&gt;
&lt;p&gt;If Slurm did not start, fear not, there is likely only a small fix required. Start by looking for an error message with&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;systemctl status slurmd.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Does it say that the &lt;code&gt;slurmd.pid&lt;/code&gt; file coud not be opened? If so, your &lt;code&gt;slurm.conf&lt;/code&gt; file probably has different values for &lt;code&gt;SlurmctldPidFile&lt;/code&gt; and &lt;code&gt;SlurmdPidFile&lt;/code&gt; than your &lt;code&gt;slurmctld.service&lt;/code&gt; file. Check by printing the latter:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;less /usr/lib/systemd/system/slurmctld.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It might show &lt;code&gt;PIDFile=/run/slurmctld.pid&lt;/code&gt;, which means that we need to change &lt;code&gt;SlurmctldPidFile&lt;/code&gt; and &lt;code&gt;SlurmdPidFile&lt;/code&gt; in our &lt;code&gt;/etc/slurm-llnl/slurm.conf&lt;/code&gt; to the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#033&#34;&gt;SlurmctldPidFile&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;/run/slurmctld.pid
&lt;span style=&#34;color:#033&#34;&gt;SlurmdPidFile&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;/run/slurmd.pid
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After that, try again to start &lt;code&gt;slurmctld&lt;/code&gt; and &lt;code&gt;slurmd&lt;/code&gt; with the above commands.&lt;/p&gt;
&lt;h1 id=&#34;sge&#34;&gt;SGE&lt;/h1&gt;
&lt;p&gt;This one is not so simple.&lt;/p&gt;
&lt;p&gt;Some parts of the following setup are taken from a public &lt;a href=&#34;https://github.com/robsyme/docker-sge/blob/master/Dockerfile&#34;&gt;Dockerfile&lt;/a&gt; created by &lt;a href=&#34;https://github.com/robsyme&#34;&gt;Robert Syme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, gain root permissions. On Ubuntu you can type &lt;code&gt;sudo -i&lt;/code&gt;. All commands have to be executed with root permissions.&lt;/p&gt;
&lt;p&gt;Then, create a new folder, let&amp;rsquo;s say via &lt;code&gt;mkdir /opt/sge/installfolder&lt;/code&gt;. Set the new folder&amp;rsquo;s location as an environment variable in the current shell via&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#366&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#033&#34;&gt;INSTALLFOLDER&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;/opt/sge/installfolder.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, execute the following commands:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#366&#34;&gt;cd&lt;/span&gt; &lt;span style=&#34;color:#033&#34;&gt;$INSTALLFOLDER&lt;/span&gt;
wget https://arc.liv.ac.uk/downloads/SGE/releases/8.1.9/sge-common_8.1.9_all.deb .
wget https://arc.liv.ac.uk/downloads/SGE/releases/8.1.9/sge-doc_8.1.9_all.deb .
wget https://arc.liv.ac.uk/downloads/SGE/releases/8.1.9/sge_8.1.9_amd64.deb .
dpkg -i ./*.deb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, download the following 4 files and place them also into the created folder, in our case into &lt;code&gt;/opt/sge/installfolder&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://tkainrad.dev/other/sge_init.sh&#34; download&gt;sge_init.sh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://tkainrad.dev/other/sge_auto_install.conf&#34; download&gt;sge_auto_install.conf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://tkainrad.dev/other/sge_hostgrp.conf&#34; download&gt;sge_hostgrp.conf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://tkainrad.dev/other/sge_exec_host.conf&#34; download&gt;sge_exec_host.conf&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Most of the magic will happen through those scripts and configuration files. After the download, we need to set some environment variables in the current shell:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#366&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#033&#34;&gt;SGE_ROOT&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;/opt/sge
&lt;span style=&#34;color:#366&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#033&#34;&gt;SGE_CELL&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;default
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We also need to set a new &lt;code&gt;profile.d&lt;/code&gt; config via&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;ln -s &lt;span style=&#34;color:#033&#34;&gt;$SGE_ROOT&lt;/span&gt;/&lt;span style=&#34;color:#033&#34;&gt;$SGE_CELL&lt;/span&gt;/common/settings.sh /etc/profile.d/sge_settings.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, execute the following to install SGE and perform setup operations:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#366&#34;&gt;cd&lt;/span&gt; &lt;span style=&#34;color:#033&#34;&gt;$SGE_ROOT&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./inst_sge -m -x -s -auto &lt;span style=&#34;color:#033&#34;&gt;$INSTALLFOLDER&lt;/span&gt;/sge_auto_install.conf &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\\&lt;/span&gt;
&lt;span style=&#34;color:#555&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; sleep &lt;span style=&#34;color:#f60&#34;&gt;10&lt;/span&gt; &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\\&lt;/span&gt;
&lt;span style=&#34;color:#555&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; /etc/init.d/sgemaster.sge-cluster restart &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\\&lt;/span&gt;
&lt;span style=&#34;color:#555&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; /etc/init.d/sgeexecd.sge-cluster restart &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\\&lt;/span&gt;
&lt;span style=&#34;color:#555&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; sed -i &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;s/HOSTNAME/`hostname`/&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#033&#34;&gt;$INSTALLFOLDER&lt;/span&gt;/sge_exec_host.conf &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\\&lt;/span&gt;
&lt;span style=&#34;color:#555&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; sed -i &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;s/HOSTNAME/`hostname`/&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#033&#34;&gt;$INSTALLFOLDER&lt;/span&gt;/sge_hostgrp.conf &lt;span style=&#34;color:#c30;font-weight:bold&#34;&gt;\\&lt;/span&gt;
&lt;span style=&#34;color:#555&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; /opt/sge/bin/lx-amd64/qconf -Me &lt;span style=&#34;color:#033&#34;&gt;$INSTALLFOLDER&lt;/span&gt;/sge_exec_host.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, our new cluster is already up and running. However, we still need to add users to the &lt;code&gt;sgeusers&lt;/code&gt; group, which was defined in the &lt;code&gt;sge_hostgrp.conf&lt;/code&gt; file you  just applied. Only users from this group are allowed to submit jobs. Therefore, we run the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;/opt/sge/bin/lx-amd64/qconf -au &amp;lt;USER&amp;gt; sgeusers
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, run &lt;code&gt;$INSTALLFOLDER/sge_init.sh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can now delete the complete &lt;code&gt;$INSTALLFOLDER&lt;/code&gt;, or at least run&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;rm &lt;span style=&#34;color:#033&#34;&gt;$INSTALLFOLDER&lt;/span&gt;/*.deb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After a restart to load the new &lt;code&gt;profile.d&lt;/code&gt; settings, all users that were added to &lt;code&gt;sgeusers&lt;/code&gt; should be able to submit jobs. Test this via:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#366&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;echo Running test from &lt;/span&gt;&lt;span style=&#34;color:#033&#34;&gt;$HOSTNAME&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt; | qsub
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id=&#34;pbs--torque&#34;&gt;PBS / Torque&lt;/h1&gt;
&lt;p&gt;This is a little simpler again. Most of the setup is taken from &lt;a href=&#34;https://jabriffa.wordpress.com/2015/02/11/installing-torquepbs-job-scheduler-on-ubuntu-14-04-lts/&#34;&gt;another blog&lt;/a&gt;. Some inspiration is also from a public Dockerfile available at &lt;a href=&#34;https://hub.docker.com/r/agaveapi/torque/&#34;&gt;Docker Hub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, gain root permissions. On Ubuntu you can type &lt;code&gt;sudo -i&lt;/code&gt;. All commands have to be executed with root permissions.&lt;/p&gt;
&lt;p&gt;We start by installing the relevant packages:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;apt-get install torque-server torque-client torque-mom torque-pam
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Installing these packages will create a default setup. Unfortunately, this is complex and would require complex changes to get to a working cluster. Instead, we stop all torque services and create a clean setup:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;/etc/init.d/torque-mom stop
/etc/init.d/torque-scheduler stop
/etc/init.d/torque-server stop
pbs_server -t create
killall pbs_server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We start by setting &lt;code&gt;localhost&lt;/code&gt; as the server host and allowing root to change the database configuration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#366&#34;&gt;echo&lt;/span&gt; localhost &amp;gt; /etc/torque/server_name
&lt;span style=&#34;color:#366&#34;&gt;echo&lt;/span&gt; localhost &amp;gt; /var/spool/torque/server_priv/acl_svr/acl_hosts
&lt;span style=&#34;color:#366&#34;&gt;echo&lt;/span&gt; root@localhost &amp;gt; /var/spool/torque/server_priv/acl_svr/operators
&lt;span style=&#34;color:#366&#34;&gt;echo&lt;/span&gt; root@localhost &amp;gt; /var/spool/torque/server_priv/acl_svr/managers
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With the following commands, we set &lt;code&gt;localhost&lt;/code&gt; also as compute node with 4 cores available:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#366&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;SERVER.DOMAIN np=4&amp;#34;&lt;/span&gt; &amp;gt; /var/spool/torque/server_priv/nodes
&lt;span style=&#34;color:#366&#34;&gt;echo&lt;/span&gt; localhost &amp;gt; /var/spool/torque/mom_priv/config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, we can already start the daemon processes again:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;/etc/init.d/torque-server start
/etc/init.d/torque-scheduler start
/etc/init.d/torque-mom start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After this &lt;code&gt;qmgr&lt;/code&gt; is ready to start the scheduler:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;qmgr -c &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;set server scheduling = true&amp;#39;&lt;/span&gt;
qmgr -c &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;set server keep_completed = 300&amp;#39;&lt;/span&gt;
qmgr -c &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;set server mom_job_sync = true&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The following commands are to create a default queue and to configure it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;qmgr -c &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;create queue batch&amp;#39;&lt;/span&gt;
qmgr -c &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;set queue batch queue_type = execution&amp;#39;&lt;/span&gt;
qmgr -c &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;set queue batch started = true&amp;#39;&lt;/span&gt;
qmgr -c &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;set queue batch enabled = true&amp;#39;&lt;/span&gt;
qmgr -c &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;set queue batch resources_default.walltime = 3:00:00&amp;#39;&lt;/span&gt;
qmgr -c &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;set queue batch resources_default.nodes = 1&amp;#39;&lt;/span&gt;
qmgr -c &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;set server default_queue = batch&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, we allow our machine to submit to the new cluster:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;qmgr -c &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;set server submit_hosts = &amp;lt;hostname&amp;gt;&amp;#39;&lt;/span&gt;
qmgr -c &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;set server allow_node_submit = true&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To test the setup, you can use the following command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;color:#366&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;echo Running test from &lt;/span&gt;&lt;span style=&#34;color:#033&#34;&gt;$HOSTNAME&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt; | qsub
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The target audience for this post is probably very small. I decided to do it anyway, as there might be someone that can benefit immensely. Personally, I would have saved a lot of time if I found such a blog post a year ago. Also, I might need it again myself at some point.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Reading List (August and September 2019)</title>
      <link>https://tkainrad.dev/posts/reading-list-augst-september-19/</link>
      <pubDate>Mon, 30 Sep 2019 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/reading-list-augst-september-19/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;You and me have something in common: We both read blog posts about software engineering.&lt;/p&gt;
&lt;p&gt;In my opinion, they are a great source of information and play an important role for the open-source ecosystem. Many projects almost depend on third-party blog posts to provide additional documentation and tutorials.&lt;/p&gt;
&lt;p&gt;Starting with today, I will regularly present blog posts, articles and other online resources that I think are worth a read. My selection criteria will be somewhat arbitrary. In general, I will try to share content that I think contains valuable information for a large spectrum of software engineering professionals. Additionally, I will provide short summaries for all the articles I post.&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;Note from the future: I did not publish regular reading lists like I planned. When I started my side-project &lt;a href=&#34;https://keycombiner.com&#34;&gt;KeyCombiner&lt;/a&gt;, other topic ideas became more interesting for me. Sorry! Please let me know if you know another blogger that does this regularly.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;So, without further ado, here are posts and other resources from the last two months that I found particularly interesting:&lt;/p&gt;
&lt;h1 id=&#34;how-i-built-a-7kmonth-wordpress-plugin-because-i-needed-the-product&#34;&gt;How I Built A $7K/Month WordPress Plugin Because I Needed The Product&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; Starter Story Blog Post&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href=&#34;http://www.lauraelizabeth.co/&#34;&gt;Laura Elizabeth&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; August 30, 2019&lt;br&gt;
&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href=&#34;https://www.starterstory.com/build-profitable-wordpress-plugin-from-scratch&#34;&gt;https://www.starterstory.com/build-profitable-wordpress-plugin-from-scratch&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I guess almost every software engineer has thought at some point about developing an app or other kind of small project that could ideally generate a consistent revenue stream, maybe in  parallel to a regular employment.&lt;/p&gt;
&lt;p&gt;In this article &lt;a href=&#34;http://www.lauraelizabeth.co/&#34;&gt;Laura Elizabeth&lt;/a&gt;   describes with impressive clarity how she managed to do just that. After reading it, you will have a very good idea about how she started her business with &lt;a href=&#34;https://www.client-portal.io/&#34;&gt;Client Portal&lt;/a&gt;, a  tool that can help enterprises work on projects together with their customers.&lt;/p&gt;
&lt;h1 id=&#34;the-boring-technology-behind-a-one-person-internet-company&#34;&gt;The boring technology behind a one-person Internet company&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; Medium Blog Post&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href=&#34;https://wenbin.org/&#34;&gt;Wenbin Fang&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; January 24, 2018&lt;br&gt;
&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href=&#34;https://broadcast.listennotes.com/the-boring-technology-behind-listen-notes-56697c2e347b&#34;&gt;https://broadcast.listennotes.com/the-boring-technology-behind-listen-notes-56697c2e347b&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Similar to the previous post, this article talks about building a one-person company. However, the difference is that this one focuses on the technical implementation and gives tips how large software products can be built with very few engineers. The author of the article is Wenbin Fang, the founder of &lt;a href=&#34;https://www.listennotes.com/de/&#34;&gt;Listen Notes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Fang fits a lot of information into a relatively short article, while managing to keep it very easy to follow.&lt;/p&gt;
&lt;h1 id=&#34;google-engineering-practices-documentation&#34;&gt;Google Engineering Practices Documentation&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; Static GitHub Pages Site&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; Google&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; September 05, 2019&lt;br&gt;
&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href=&#34;https://google.github.io/eng-practices/&#34;&gt;https://google.github.io/eng-practices/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In September, Google has published resources on their internal engineering practices. Currently, it is mainly focused on the code reviewing process from both the reviewer&amp;rsquo;s perspective and from the perspective of the code author. Much of the stuff in there is rather straightforward, but it is very nice to have all of this documented in a condensed fashion.&lt;/p&gt;
&lt;p&gt;If your company struggles with code reviewing, this might be the resource to find ideas for improving your process.&lt;/p&gt;
&lt;h1 id=&#34;software-architecture-guide&#34;&gt;Software Architecture Guide&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; Personal Website&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href=&#34;https://martinfowler.com/&#34;&gt;Martin Fowler&lt;/a&gt; et al.&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; August 01, 2019&lt;br&gt;
&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href=&#34;https://martinfowler.com/architecture/&#34;&gt;https://martinfowler.com/architecture/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Martin Fowler has published a guide to material on his blog about popular topics related to Software Architecture, such as Micro Frontends, Serverless, and Enterprise Architecture.&lt;/p&gt;
&lt;p&gt;His introduction resonates a lot with me:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Like many in the software world, I’ve long been wary of the term “architecture” as it often suggests a separation from programming and an unhealthy dose of pomposity. But I resolve my concern by emphasizing that good architecture is something that supports its own evolution, and is deeply intertwined with programming.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I too have some issues with identifying as a &lt;em&gt;Software Architect&lt;/em&gt; even though it is written on my company business cards. I believe a good software engineer is both an architect and a developer.&lt;/p&gt;
&lt;p&gt;In my opinion, the resources collected in Fowler&amp;rsquo;s Guide are of very high quality and can serve as excellent references for the respective concepts.&lt;/p&gt;
&lt;h1 id=&#34;software-architecture-is-overrated-clear-and-simple-design-is-underrated&#34;&gt;Software Architecture is Overrated, Clear and Simple Design is Underrated&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; Personal Website&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href=&#34;https://blog.pragmaticengineer.com/&#34;&gt;Gergely Orosz&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; September 17, 2019&lt;br&gt;
&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href=&#34;https://blog.pragmaticengineer.com/software-architecture-is-overrated/&#34;&gt;https://blog.pragmaticengineer.com/software-architecture-is-overrated/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://blog.pragmaticengineer.com/&#34;&gt;Gergely Orosz&lt;/a&gt; might not appreciate the lengthy abstract articles from the resource above. In his post, he argues that formal architecture practices are overrated and engineers should instead focus on simple designs.&lt;/p&gt;
&lt;p&gt;I do agree with Orosz that teams should be careful not to get sidetracked while designing an overly complicated architecture. Of course, the idea is not to skip design steps, but to create a more friction-less process. I recommend the article especially to engineers working in large enterprises, where architecture documents often have to traverse several hierarchy levels before decisions are made.&lt;/p&gt;
&lt;h1 id=&#34;why-our-team-cancelled-our-move-to-microservices&#34;&gt;Why our team cancelled our move to microservices&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Type:&lt;/strong&gt; Medium Blog Post&lt;br&gt;
&lt;strong&gt;Author:&lt;/strong&gt; Steven Lemon&lt;br&gt;
&lt;strong&gt;Date:&lt;/strong&gt; August 09, 2019&lt;br&gt;
&lt;strong&gt;Link:&lt;/strong&gt; &lt;a href=&#34;https://medium.com/@steven.lemon182/why-our-team-cancelled-our-move-to-microservices-8fd87898d952&#34;&gt;https://medium.com/@steven.lemon182/why-our-team-cancelled-our-move-to-microservices-8fd87898d952&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Chances are you already know this one. It caused a lot of discussion, especially on &lt;a href=&#34;https://www.reddit.com/r/programming/comments/co6u7y/why_our_team_cancelled_our_move_to_microservices/&#34;&gt;Reddit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The post is about the efforts of an engineering team to break up a software monolith into microservices. As you can guess from the title, the endeavour turned out to be more difficult than expected and was ultimately cancelled. Even though many of the identified issues seem rather obvious, I think this post is an excellent read for everyone preparing to break up monoliths. You might want to take a step back and think about whether your organization is really in a position to benefit from various independent service applications.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I hope you found something that is of interest to you. If you would like to point out other recent articles, you are very welcome to share them in the comments below or privately via &lt;a href=&#34;mailto:thomas@tkainrad.dev&#34;&gt;email&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Setting up a Linux Workstation for Software Development</title>
      <link>https://tkainrad.dev/posts/setting-up-linux-workstation/</link>
      <pubDate>Tue, 13 Aug 2019 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/setting-up-linux-workstation/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;In my opinion, Linux is the best operating system for a software engineer, except they develop explicitly for Windows or macOS. Whenever I read about new features for the &lt;a href=&#34;https://docs.microsoft.com/en-us/windows/wsl/about&#34;&gt;Windows Subsystem for Linux&lt;/a&gt;, I wonder why people settle for anything less than the real thing. Linux aids many of my workflows with its customizability, superior command-line experience, native support for containerization platforms, a dependable package management system, and many other features that I will describe in this blog post. macOS comes close, but, in my opinion, falls short due to exorbitant hardware costs and less room for customization.&lt;/p&gt;
&lt;p&gt;Still, as with any other operating system, optimizing a Linux workstation for software development requires some configuration. In this post, I will present tools and concepts that I think are very helpful, but overlooked by many. I will try to show only things that are not specific to my workflows, but rather useful for almost any software engineer, DevOps specialist, or just regular productivity enthusiast. Naturally, many of the configuration options and tools covered in this post are subjective. Nevertheless, I am confident that my suggestions are a good place to start. They have served me well over the years and I continually try to optimize my setup. Hence, I very much appreciate any improvement you might suggest.&lt;/p&gt;
&lt;h1 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;A Linux machine&lt;/strong&gt;&lt;br&gt;
For this guide, I will use Ubuntu 20.04, which is the current LTS release. I am very happy with Ubuntu. Anything I don&amp;rsquo;t like can easily be changed. The biggest advantage over many other distributions is probably that the Ubuntu community is very large and it is usually easy to find people with similar problems. There is also a very active &lt;a href=&#34;https://askubuntu.com/&#34;&gt;StackExchange community&lt;/a&gt;.  However, most of the things covered in this post will apply to pretty much any Linux distribution and even macOS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some command-line experience&lt;/strong&gt;&lt;br&gt;
Much of this post is intended for people who feel right at home when they see a &lt;code&gt;~&lt;/code&gt;- character. If you are not comfortable working with command-lines and have no intention to change this, you can still read the other sections though.&lt;/p&gt;
&lt;h1 id=&#34;tweaking-ubuntu&#34;&gt;Tweaking Ubuntu&lt;/h1&gt;
&lt;p&gt;Before we start any serious work, I will point out some changes to Ubuntu itself that I apply on all my machines.&lt;/p&gt;
&lt;h2 id=&#34;appearance&#34;&gt;Appearance&lt;/h2&gt;
&lt;p&gt;One of the first things I do after installing Ubuntu is to install the &lt;em&gt;GNOME Tweak Tool&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo apt-get install gnome-tweak-tool
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It enables complex modifications of the GNOME desktop environment. I recommend you play around with some of its options. For this guide, we will only consider the &lt;em&gt;Appearance&lt;/em&gt; section. Specifically, we want to change the default Ubuntu theme to another one that provides better contrasts and an overall cleaner experience, namely the &lt;a href=&#34;https://github.com/adapta-project/adapta-gtk-theme&#34;&gt;Adapta-gtk-theme&lt;/a&gt;, which is based on the Material Design guidelines.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo apt-get install adapta-gtk-theme
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After installing the theme, you should restart the &lt;em&gt;GNOME Tweak Tool&lt;/em&gt;, and you can then select &lt;em&gt;Adapta-Eta&lt;/em&gt; as your new theme. The theme comes with some nice benefits that you wouldn&amp;rsquo;t think of at first. For example, it fixes the ridiculously small toolbar icons in the Eclipse IDE.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/5/adapta-elements.png&#34;
         alt=&#34;Adapta Theme elements (Source).&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Adapta Theme elements (&lt;a href=&#34;https://github.com/adapta-project/adapta-gtk-theme&#34;&gt;Source&lt;/a&gt;).&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;


&lt;div class=&#34;notices warning&#34;&gt;
    &lt;p&gt;Unfortunately, the Adapta theme is no longer actively maintained. It is still a perfectly fine theme to use and if you notice any glitches, you can always switch themes with a couple clicks. A possible alternative is &lt;a href=&#34;https://github.com/nana-4/materia-theme&#34;&gt;Materia&lt;/a&gt;, which also works well with the remainder of this blog post.&lt;/p&gt;

&lt;/div&gt;
&lt;h2 id=&#34;nautilus-type-ahead-search&#34;&gt;Nautilus Type-Ahead Search&lt;/h2&gt;
&lt;p&gt;The next thing I suggest is to change the Nautilus search behavior. With recent Ubuntu distributions, there is no type-ahead search in the standard file explorer. This means you can not start to type some letters and jump between files that start with these letters. Instead, Nautilus will enter a dedicated search mode that is confusing and quite slow for folders with a large number of files.&lt;/p&gt;
&lt;p&gt;Fortunately, we can easily get the old behavior back:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo add-apt-repository ppa:lubomir-brindza/nautilus-typeahead
sudo apt dist-upgrade
killall nautilus &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# to force a restart&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/5/type-ahead-search.gif&#34;
         alt=&#34;Searching through my /bin folder by typing ahead (Source).&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Searching through my &lt;code&gt;/bin&lt;/code&gt; folder by typing ahead (&lt;a href=&#34;https://github.com/adapta-project/adapta-gtk-theme&#34;&gt;Source&lt;/a&gt;).&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;There is a &lt;a href=&#34;https://askubuntu.com/a/278553/188679&#34;&gt;StackExchange answer&lt;/a&gt; that provides further details.&lt;/p&gt;
&lt;h2 id=&#34;hibernation&#34;&gt;Hibernation&lt;/h2&gt;
&lt;p&gt;Another thing that is essential for me is hibernation. Usually, I will only shutdown my computer once a week or even less. However, I am not a big fan of suspending for long periods of time. Power outages could have unforeseen consequences and I simply feel a little better if my machines are off the grid altogether.&lt;/p&gt;
&lt;p&gt;Unfortunately, setting up hibernation is tricky and can depend on your hardware. I updated this section multiple times already to contain the most robust solution, but you never quite know.
If you don&amp;rsquo;t care about hibernation, you should probably skip this section and spare yourself the trouble.&lt;/p&gt;
&lt;p&gt;In principle, hibernation is possible out of the box. Just type &lt;code&gt;sudo systemctl hibernate&lt;/code&gt;. However, this is likely to fail with an error. My fix for this relies on a mixture of &lt;a href=&#34;https://linuxize.com/post/how-to-add-swap-space-on-ubuntu-20-04/#creating-a-swap-file&#34;&gt;this&lt;/a&gt; and &lt;a href=&#34;https://rephlex.de/blog/2019/12/27/how-to-hibernate-and-resume-from-swap-file-in-ubuntu-20-04-using-full-disk-encryption/&#34;&gt;that&lt;/a&gt; blog post. First, we need to find out the current size of our swap file:&lt;/p&gt;

&lt;div class=&#34;notices warning&#34;&gt;
    &lt;p&gt;The below instructions assume that you use Ubuntu 20.04 with a swap file. This is the default for new installations. However, if you upgraded from a previous version, you might still have a swap partition. In this case, the process is similar, but not covered here.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo swapon --show &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# or `cat /proc/swaps`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If this is less than your main memory (RAM), you will need to increase it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo swapoff -a
sudo fallocate -l &amp;lt;SIZE&amp;gt;G /swapfile
sudo chmod &lt;span style=&#34;color:#f60&#34;&gt;600&lt;/span&gt; /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo swapon --show &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# to verify changed size&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, we need to find out the UUID and offset of our now large enough swapfile:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;sudo swap-offset /swapfile
&amp;gt; &amp;lt;OFFSET&amp;gt;
sudo findmnt -no SOURCE,UUID -T /swapfile 
&amp;gt; &amp;lt;UUID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With this, we are almost done. Using the UUID and the file offset, we edit the following line in &lt;code&gt;/etc/default/grub&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#c30&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#033&#34;&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;quiet splash resume=UUID=&amp;lt;UUID&amp;gt; resume_offset=&amp;lt;OFFSET&amp;gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, we create a &lt;code&gt;conf.d&lt;/code&gt; resume file. Simply do &lt;code&gt;sudo nano /etc/initramfs-tools/conf.d/resume&lt;/code&gt; and add the content &lt;code&gt;RESUME=UUID=&amp;lt;UUID&amp;gt;&lt;/code&gt; to the file. At last, we need to update grup and initramfs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo update-grub
sudo update-initramfs -u -k all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And finally, you should be able to do &lt;code&gt;sudo systemctl hibernate&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Please let me know if this approach doesn&amp;rsquo;t work on your system. I would like to include potential problems in this post, if there are any.&lt;/p&gt;
&lt;h1 id=&#34;boosting-command-line-productivity&#34;&gt;Boosting Command-Line Productivity&lt;/h1&gt;
&lt;p&gt;I believe that command-line proficiency can significantly boost developer productivity. This is especially true in times of DevOps and NoOps, where developers are supposed to handle deployment pipelines and cloud infrastructure. Fortunately, there are some tools that can help you become a command-line wizard.&lt;/p&gt;
&lt;h2 id=&#34;shortcuts&#34;&gt;Shortcuts&lt;/h2&gt;
&lt;p&gt;Before going forward, I would suggest to set up some general terminal shortcuts. Simply go to &lt;em&gt;Edit -&amp;gt; Preferences -&amp;gt; Shortcuts&lt;/em&gt;. I like to replicate web browser shortcuts for opening new tabs and windows, closing tabs, and cycling between tabs.&lt;/p&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;Did you know that all common terminal emulators have an Undo shortcuts with &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;-&lt;/kbd&gt;?
Among many other things, my side-project &lt;a href=&#34;https://keycombiner.com&#34;&gt;KeyCombiner&lt;/a&gt; has a public &lt;a href=&#34;https://keycombiner.com/collecting/collections/public/136&#34;&gt;collection of terminal shortcuts&lt;/a&gt;, many of which are barely known and still very useful.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;However, using this interface, it is not possible to set up &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Tab&lt;/kbd&gt; and &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;Tab&lt;/kbd&gt; as tab switching shortcuts. Gnome has some issues with using the &lt;kbd&gt;Tab&lt;/kbd&gt; key in shortcut configurations. Thanks to &lt;a href=&#34;https://askubuntu.com/a/875482/188679&#34;&gt;this answer from the Ubuntu community&lt;/a&gt;, we can get around this. Simply execute the following in your terminal:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;gsettings &lt;span style=&#34;color:#366&#34;&gt;set&lt;/span&gt; org.gnome.Terminal.Legacy.Keybindings:/org/gnome/terminal/legacy/keybindings/ next-tab &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;&amp;lt;Primary&amp;gt;Tab&amp;#39;&lt;/span&gt;
gsettings &lt;span style=&#34;color:#366&#34;&gt;set&lt;/span&gt; org.gnome.Terminal.Legacy.Keybindings:/org/gnome/terminal/legacy/keybindings/ prev-tab &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;&amp;lt;Primary&amp;gt;&amp;lt;Shift&amp;gt;Tab&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;switching-from-bash-to-zsh&#34;&gt;Switching from Bash to Zsh&lt;/h2&gt;
&lt;p&gt;A couple of months ago, I made an important change in my life. I switched from Bash to Zsh.&lt;/p&gt;
&lt;p&gt;There are a lot of blog posts, threads, and articles that compare Bash and Zsh, e.g.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.howtogeek.com/362409/what-is-zsh-and-why-should-you-use-it-instead-of-Bash/&#34;&gt;What is ZSH, and Why Should You Use It Instead of Bash?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://stackabuse.com/zsh-vs-bash/&#34;&gt;Zsh vs Bash&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/wearetheledger/oh-my-zsh-made-for-cli-lovers-installation-guide-3131ca5491fb&#34;&gt;Oh-My-Zsh! A Work of CLI Magic — Tutorial for Ubuntu&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the end it boils down to one thing: &lt;strong&gt;Plugin and theme support&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Zsh has an amazing themes and plugins ecosystem. Theme support allows you to easily get a beautiful shell. Plugins range from simple auto-completion for all kinds of applications and tools(git, mvn, hugo, &amp;hellip;) to complex stuff like improved search capabilities. Because of this fact alone, Zsh is superior to Bash.&lt;/p&gt;
&lt;p&gt;However, chances are that you grew fond of Bash and that you get a bad feeling when somebody tells you that there is something better. I understand, it was the same for me. Still, I urge you to give it a shot. The transition is very painless since pretty much everything you like about Bash still works with Zsh. You will not have to learn new commands.&lt;/p&gt;
&lt;h3 id=&#34;installing-zsh&#34;&gt;Installing Zsh&lt;/h3&gt;
&lt;p&gt;Installing Zsh on Ubuntu is very easy. We can use the regular package manager:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo apt install zsh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, we set Zsh as our new default shell:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;chsh -s &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;$(&lt;/span&gt;which zsh&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;The &lt;code&gt;chsh&lt;/code&gt; command updates the &lt;code&gt;/etc/passwd&lt;/code&gt; file, but does not affect the current session. You need to log out and log in again to have zsh as your default shell.&lt;/p&gt;

&lt;/div&gt;
&lt;h3 id=&#34;configuring-zsh-history&#34;&gt;Configuring Zsh History&lt;/h3&gt;
&lt;p&gt;For me there are few things worse than searching for an old shell command only to find out that the shell&amp;rsquo;s history size was set to a ridiculously low default number and that, because of this, the command was not saved.&lt;/p&gt;
&lt;p&gt;Therefore, we extend the Zsh history size by putting this at the end of the &lt;code&gt;~/.zshrc&lt;/code&gt; configuration file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#033&#34;&gt;HISTSIZE&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;10000000&lt;/span&gt;
&lt;span style=&#34;color:#033&#34;&gt;SAVEHIST&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f60&#34;&gt;10000000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For me, the command history is a tool to search for previous commands I might need now. I do not use it as a protocol for what I did when and in which exact order. Therefore, I use the following options to get rid of all duplicates in my history automatically:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;## History command configuration&lt;/span&gt;
    
setopt HIST_IGNORE_ALL_DUPS &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# ignore duplicated commands history list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Also, I like to use multiple terminal sessions in parallel, potentially working on the same problem. For this, it helps a lot to share the same history among all sessions. It can be accomplished by&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;setopt SHARE_HISTORY &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# share command history data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you are transitioning from Bash to Zsh, you probably want to keep your existing Bash history. In contrast to Bash, Zsh stores timestamps in its history file. Therefore, re-using the Bash history file does not work out of the box. Fortunately, all we need to do is to append some timestamp to all the commands from our previous Bash history and put them into the new Zsh history file. You could easily script this yourself or use one of the many good scripts that other people wrote, &lt;a href=&#34;https://gist.github.com/goyalankit/a1c88bfc69107f93cda1&#34;&gt;such as this one&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;zsh-features-that-are-not-in-bash&#34;&gt;Zsh Features that are not in Bash&lt;/h3&gt;
&lt;p&gt;Once we have Zsh installed and finished our basic history configuration, we can start to take a look at some out-of-the-box features that make it stand out in comparison with Bash. If these do not impress you, keep in mind that the real big thing is plugin support, which I cover &lt;a href=&#34;#installing-zsh-plugins&#34;&gt;below&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;recursive-path-expansion&#34;&gt;Recursive Path Expansion&lt;/h4&gt;
&lt;p&gt;Zsh can do much more sophisticated expansions than Bash. This is documented in detail in &lt;a href=&#34;http://zsh.sourceforge.net/Doc/Release/Expansion.html&#34;&gt;the Zsh manual&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One feature that you can easily start to use is recursive path expansion. If you type &lt;code&gt;cd /usr/local/b&lt;/code&gt; in Bash, you can then press &lt;code&gt;Tab&lt;/code&gt; to expand this to &lt;code&gt;cd /usr/local/bin&lt;/code&gt;. With Zsh, you can also type &lt;code&gt;cd /u/lo/b&lt;/code&gt; and then press &lt;code&gt;Tab&lt;/code&gt;. Zsh will recursively expand all path elements:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/5/recursive-path-expansion.gif&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;The above animation shows also that you can omit &lt;code&gt;cd&lt;/code&gt; when changing directories.&lt;/p&gt;
&lt;h4 id=&#34;calculator-zcalc&#34;&gt;Calculator: zcalc&lt;/h4&gt;
&lt;p&gt;Before switching to Zsh, I was often using the Python Shell as a calculator. However, this has a few downsides. Obviously, the Python Shell is not primarily developed to be a good calculator app. For example,  simple operations, such as &lt;code&gt;sqrt&lt;/code&gt;, require import statements, and there are no convenient ways to access results of previous lines.&lt;/p&gt;
&lt;p&gt;The calculator script shipped with Zsh is a very simple and efficient solution. Before using it, it has to be loaded:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;autoload -Uz zcalc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you plan to use it often, you can add the &lt;code&gt;autoload&lt;/code&gt; command to your &lt;code&gt;~/.zshrc&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then, start the calculator with &lt;code&gt;zcalc&lt;/code&gt; and do some calculations:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/5/zcalc.gif&#34;
         alt=&#34;Results of previous operations can be accessed with the $-character.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Results of previous operations can be accessed with the &lt;code&gt;$&lt;/code&gt;-character.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;advanced-aliasing&#34;&gt;Advanced Aliasing&lt;/h4&gt;
&lt;p&gt;Zsh has some unique aliasing capabilities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Suffix Aliases&lt;/strong&gt;&lt;br&gt;
This means you can assign applications to file suffixes, e.g. &lt;code&gt;alias -s txt=vscode&lt;/code&gt; will allow you to open a &lt;code&gt;.txt&lt;/code&gt; file with Visual Studio Code by just typing &lt;code&gt;file.txt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Global Aliases&lt;/strong&gt;&lt;br&gt;
Global aliases are set with the &lt;code&gt;-g&lt;/code&gt; flag and allow to specify a replacement for any part of a command. For example, if you set &lt;code&gt;alias -g G=&#39; | grep&#39; &lt;/code&gt; you will be able to do the following:
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/5/global-alias.gif&#34;/&gt; 
&lt;/figure&gt;
&lt;/p&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;To make aliases permanent, it is required to add them to your &lt;code&gt;~/.zshrc&lt;/code&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;h3 id=&#34;installing-zsh-plugins&#34;&gt;Installing ZSH plugins&lt;/h3&gt;
&lt;p&gt;Finally, it is time to explore the real strength of Zsh, which means installing plugins to boost our productivity above anything this anime character has ever seen before:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/5/vegeta.gif&#34;/&gt; 
&lt;/figure&gt;

&lt;h4 id=&#34;package-manager-oh-my-zsh&#34;&gt;Package Manager: Oh my Zsh&lt;/h4&gt;
&lt;p&gt;A good place to start is to install &lt;a href=&#34;https://github.com/robbyrussell/oh-my-zsh&#34;&gt;Oh my Zsh&lt;/a&gt;, &amp;ldquo;a community-driven framework for managing your Zsh configuration.&amp;rdquo; Simply do:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Copy the template configuration file &lt;code&gt;.zshrc.zsh-template&lt;/code&gt; in the .&lt;code&gt;zshrc&lt;/code&gt; home directory and apply the configuration by executing the source command. Both can be done at once by executing the following line:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Oh my Zsh&lt;/em&gt; comes with many themes and plugins that we only have to enable in the configuration file in order to use them. In the next section, we will start by selecting a pretty theme.&lt;/p&gt;
&lt;h4 id=&#34;theme-agnoster&#34;&gt;Theme: Agnoster&lt;/h4&gt;
&lt;p&gt;Personally, I like the &lt;em&gt;agnoster&lt;/em&gt; theme. It works well with the Adapta-Eta Gnome theme we have set &lt;a href=&#34;#appearance&#34;&gt;previously&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To enable a theme, set ZSH_THEME to the name of the theme in your &lt;code&gt;~/.zshrc&lt;/code&gt;, in my case this is  &lt;code&gt;ZSH_THEME=agnoster&lt;/code&gt;. If you have troubles with special characters not being shown properly, install the Powerline font package:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo apt-get install fonts-powerline
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;em&gt;Oh my Zsh&lt;/em&gt; GitHub wiki provides a &lt;a href=&#34;https://github.com/robbyrussell/oh-my-zsh/wiki/Themes&#34;&gt;well curated list of available themes along with screenshots&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All pictures and animations of terminals you see in this post show the Agnoster theme with slight modifications to the prompt. In particular, I removed the git-part of the prompt and added a newline part instead. If you would like to replicate this, edit your  &lt;code&gt;~/.oh-my-zsh/themes/agnoster.zsh-theme&lt;/code&gt; and replace the &lt;code&gt;## Main prompt&lt;/code&gt; part close to the end with the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;21
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;22
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;23
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;24
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;25
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;prompt_newline&lt;span style=&#34;color:#555&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;{&lt;/span&gt;
  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;[[&lt;/span&gt; -n &lt;span style=&#34;color:#033&#34;&gt;$CURRENT_BG&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;]]&lt;/span&gt;; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;then&lt;/span&gt;
    &lt;span style=&#34;color:#366&#34;&gt;echo&lt;/span&gt; -n &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34; %{%k%F{&lt;/span&gt;&lt;span style=&#34;color:#033&#34;&gt;$CURRENT_BG&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;}%}&lt;/span&gt;&lt;span style=&#34;color:#033&#34;&gt;$SEGMENT_SEPARATOR&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;%{%k%F{&lt;/span&gt;&lt;span style=&#34;color:#033&#34;&gt;$CURRENT_BG&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;}%}&lt;/span&gt;&lt;span style=&#34;color:#033&#34;&gt;$SEGMENT_SEPARATOR&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;else&lt;/span&gt;
    &lt;span style=&#34;color:#366&#34;&gt;echo&lt;/span&gt; -n &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34; %{%k%}&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;fi&lt;/span&gt;

  &lt;span style=&#34;color:#366&#34;&gt;echo&lt;/span&gt; -n &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34; %{%f%}&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#033&#34;&gt;CURRENT_BG&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#555&#34;&gt;}&lt;/span&gt;

&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;## Main prompt&lt;/span&gt;
build_prompt&lt;span style=&#34;color:#555&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;{&lt;/span&gt;
  &lt;span style=&#34;color:#033&#34;&gt;RETVAL&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#033&#34;&gt;$?&lt;/span&gt;
  prompt_status
  prompt_virtualenv
  prompt_aws
  prompt_context
  prompt_dir
  prompt_bzr
  prompt_hg
  prompt_newline
  prompt_end
&lt;span style=&#34;color:#555&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Additionally, I recommend changing the font color of the directory part of the prompt from black to white. In my opinion, this provides better contrast with the dark blue background:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# Dir: current working directory&lt;/span&gt;
prompt_dir&lt;span style=&#34;color:#555&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;{&lt;/span&gt;
  prompt_segment blue white &lt;span style=&#34;color:#c30&#34;&gt;&amp;#39;%~&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#555&#34;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;basic-plugins&#34;&gt;Basic Plugins&lt;/h4&gt;
&lt;p&gt;Next, you should enable some included plugins via adding them to your &lt;code&gt;plugins&lt;/code&gt; setting in your &lt;code&gt;~/.zshrc&lt;/code&gt; file. After this step, it might look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#033&#34;&gt;plugins&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=(&lt;/span&gt;colored-man-pages git python django mvn aws docker vscode&lt;span style=&#34;color:#555&#34;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can browse all the plugins that come with &lt;em&gt;Oh my Zsh&lt;/em&gt; in the official &lt;a href=&#34;https://github.com/robbyrussell/oh-my-zsh/tree/master/plugins&#34;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The above listed plugins are rather trivial, they provide command completion and define a set of aliases and shortcuts that come in handy when working with the respective tool. Nevertheless, this provides a real productivity boost. For example, finding out which git commands start with &lt;code&gt;r&lt;/code&gt; becomes much simpler:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/5/git-r.gif&#34;/&gt; 
&lt;/figure&gt;

&lt;h4 id=&#34;jump-to-frequently-used-directories&#34;&gt;Jump to Frequently Used Directories&lt;/h4&gt;
&lt;p&gt;Most command-line work happens in just a handful of directories. For me, these are primarily the root folders of various software development projects. Of course, I could manually set aliases for these folders so that I can quickly switch between them, but this would be tedious.&lt;/p&gt;
&lt;p&gt;Fortunately, there is a better solution with &lt;a href=&#34;https://github.com/rupa/z&#34;&gt;z&lt;/a&gt;. It tracks your most used directories and will take you to them via &lt;code&gt;z &amp;lt;query&amp;gt;&lt;/code&gt;. The &lt;code&gt;query&lt;/code&gt; can be any part of target directory&amp;rsquo;s path:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/gifs/z.gif&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;The software is already bundled with &lt;em&gt;Oh my Zsh&lt;/em&gt;, simply add z to the list of plugins:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#033&#34;&gt;plugins&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=(&lt;/span&gt;z colored-man-pages git python django mvn aws docker vscode&lt;span style=&#34;color:#555&#34;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I could have probably included this one in the list of basic plugins above, but I wanted to highlight it because I find it particularly useful.&lt;/p&gt;
&lt;h4 id=&#34;supercharging-history-search&#34;&gt;Supercharging History Search&lt;/h4&gt;
&lt;p&gt;The original Bash history search always bothered me. Repeatedly pressing &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;r&lt;/kbd&gt; and hoping to get lucky at some point is very cumbersome if you have lots of similar commands containing the query. To be fair, there are also great additions for Bash that improve this situation, such as &lt;a href=&#34;https://github.com/cantino/mcfly&#34;&gt;McFly&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, this is about improving history search with Zsh. I believe the best solution is using &lt;a href=&#34;https://github.com/jimeh/zsh-peco-history&#34;&gt;peco-history&lt;/a&gt;. It is a Zsh plugin that relies on &lt;a href=&#34;https://github.com/peco/peco&#34;&gt;peco&lt;/a&gt; to interactively filter through your history file. Peco is a general-purpose interactive filtering tool that you can (and should!) also use as a stand-alone tool in your terminal. On Ubuntu, we can install it from the official repositories:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo apt-get install peco
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Afterwards clone &lt;a href=&#34;https://github.com/jimeh/zsh-peco-history&#34;&gt;zsh-peco-history&lt;/a&gt; into the &lt;code&gt;$ZSH_CUSTOM/plugins&lt;/code&gt; directory. Zsh will automatically resolve the pre-set shell variable:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;git clone https://github.com/jimeh/zsh-peco-history.git &lt;span style=&#34;color:#033&#34;&gt;$ZSH_CUSTOM&lt;/span&gt;/plugins/zsh-peco-history
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, add &lt;code&gt;zsh-peco-history&lt;/code&gt; to the list of plugins:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#033&#34;&gt;plugins&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=(&lt;/span&gt;zsh-peco-history z colored-man-pages git python django mvn aws docker vscode&lt;span style=&#34;color:#555&#34;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The plugin will be ready once you start a new terminal session.&lt;/p&gt;
&lt;p&gt;The following animation shows that this improved history search was very helpful for writing this blog post. It allowed me to easily search for the exact commands I used when I set up my current machine:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/gifs/peco-search.gif&#34;
         alt=&#34;Searching shell history with peco.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Searching shell history with &lt;a href=&#34;https://github.com/peco/peco&#34;&gt;peco&lt;/a&gt;.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;autosuggestions&#34;&gt;Autosuggestions&lt;/h4&gt;
&lt;p&gt;Our new history search is great. The bad news is that we will not use it very often, because there is something that is even better in most cases: &lt;a href=&#34;https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md&#34;&gt;zsh-autosuggestions&lt;/a&gt;&lt;br&gt;
This plugin suggests completions for your commands as you type. By default, the suggested completion will be the first match in your history. This is how it looks like:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/gifs/autosuggestions.gif&#34;
         alt=&#34;Using a suggested git command-line.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Using a suggested &lt;code&gt;git&lt;/code&gt; command-line.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;git clone https://github.com/zsh-users/zsh-autosuggestions &lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#033&#34;&gt;ZSH_CUSTOM&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;:-&lt;/span&gt;~/.oh-my-zsh/custom&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;/plugins/zsh-autosuggestions
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Afterward, add &lt;code&gt;zsh-autosuggestions&lt;/code&gt; to your plugins setting in your &lt;code&gt;~/.zshrc&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#033&#34;&gt;plugins&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=(&lt;/span&gt;zsh-autosuggestions zsh-peco-history z colored-man-pages git python django mvn aws docker vscode&lt;span style=&#34;color:#555&#34;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;syntax-highlighting&#34;&gt;Syntax Highlighting&lt;/h4&gt;
&lt;p&gt;Another great plugin is &lt;a href=&#34;https://github.com/zsh-users/zsh-syntax-highlighting&#34;&gt;zsh-syntax-highlighting&lt;/a&gt;. It highlights commands while you are typing them. This is especially useful for catching syntax errors. Once again, install via git:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;git clone https://github.com/zsh-users/zsh-syntax-highlighting.git &lt;span style=&#34;color:#a00&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#033&#34;&gt;ZSH_CUSTOM&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;:-&lt;/span&gt;~/.oh-my-zsh/custom&lt;span style=&#34;color:#a00&#34;&gt;}&lt;/span&gt;/plugins/zsh-syntax-highlighting
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, add &lt;code&gt;zsh-syntax-highlighting&lt;/code&gt; to your plugins setting in your &lt;code&gt;~/.zshrc&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#033&#34;&gt;plugins&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=(&lt;/span&gt;zsh-syntax-highlighting zsh-autosuggestions zsh-peco-history z colored-man-pages git python django mvn aws docker vscode&lt;span style=&#34;color:#555&#34;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/5/syntax-highlighting.gif&#34;
         alt=&#34;Syntax highlighting also indicates whether a command is typed correctly.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Syntax highlighting also indicates whether a command is typed correctly.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;clipboard-tool&#34;&gt;Clipboard Tool&lt;/h4&gt;
&lt;p&gt;This one is not really a full-blown plugin. &lt;em&gt;Oh my Zsh&lt;/em&gt; comes with a script called &lt;code&gt;clipcopy&lt;/code&gt; out of the box. It is a simple tool that enables clipboard copy and past operations from the command-line:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&amp;lt;command&amp;gt; | clipcopy    &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# copies stdin to clipboard&lt;/span&gt;
clipcopy &amp;lt;file&amp;gt;         &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# copies the content of a file to clipboard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If this doesn&amp;rsquo;t work, you are probably missing &lt;code&gt;xclip&lt;/code&gt;. Install it via&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo apt-get install xclip 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Somehow, I have a hard time remembering the name &lt;em&gt;clipcopy&lt;/em&gt;, therefore I set a global alias as &lt;a href=&#34;https://tkainrad.dev/posts/setting-up-linux-workstation/#advanced-aliasing&#34;&gt;described above&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#366&#34;&gt;alias&lt;/span&gt; -g &lt;span style=&#34;color:#033&#34;&gt;ctrlc&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#39; | clipcopy&amp;#39;&lt;/span&gt;
&lt;span style=&#34;color:#366&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;copythisinclipboard&amp;#34;&lt;/span&gt; ctrlc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;If you don&amp;rsquo;t use &lt;em&gt;Oh my Zsh&lt;/em&gt;, you can get the functionality of &lt;em&gt;clipcopy&lt;/em&gt; via &lt;a href=&#34;https://github.com/twang817/zsh-clipboard&#34;&gt;an actual plugin&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;h1 id=&#34;ssh-setup&#34;&gt;SSH Setup&lt;/h1&gt;
&lt;p&gt;One of the great advantages of using Linux instead of Windows is the out-of-the-box SSH support. Immediately after installing your distribution, you can start to access remote servers.&lt;/p&gt;
&lt;p&gt;However, there is some room for streamlining your SSH tasks. You can avoid lots of repetitive typing if you modify your &lt;code&gt;.ssh/config&lt;/code&gt; file and setup meaningful defaults for the machines you access regularly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;Host ec2-instance &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# you can freely chose a name here that will act as alias for this host&lt;/span&gt;
    HostName &amp;lt;ip-address or hostname&amp;gt;
    User tkainrad


Host cluster &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# you can freely chose a name here that will act as alias for this host&lt;/span&gt;
    HostName &amp;lt;ip-address or hostname&amp;gt;
    User tkainrad
    IdentityFile &lt;span style=&#34;color:#c30&#34;&gt;`&lt;/span&gt;/home/tkainrad/cluster_rsa.pub&lt;span style=&#34;color:#c30&#34;&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For more sophisticated settings, look up the &lt;a href=&#34;https://linux.die.net/man/5/ssh_config&#34;&gt;&lt;code&gt;ssh_config&lt;/code&gt; man page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to go one step further, you can set up public key authentication so you can login to your most-used remote hosts without having to re-enter your password each time.
With &lt;a href=&#34;https://www.ssh.com/ssh/copy-id&#34;&gt;&lt;code&gt;ssh-copy-id&lt;/code&gt;&lt;/a&gt;, there is a simple tool that streamlines this process. If you have your hosts already configured via the &lt;code&gt;.ssh/config&lt;/code&gt; file, just do&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;ssh-copy-id ec2-instance
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By default, &lt;code&gt;ssh-copy-id&lt;/code&gt; will take your public key in &lt;code&gt;~.ssh/id_rsa.pub&lt;/code&gt;. If you want to use different keys for each of your remote hosts, check out the &lt;a href=&#34;http://manpages.ubuntu.com/manpages/trusty/man1/ssh-copy-id.1.html&#34;&gt;man page&lt;/a&gt; page or &lt;a href=&#34;https://www.howtogeek.com/tips/bypass-ssh-logins-by-adding-your-key-to-a-remote-server-in-a-single-command/&#34;&gt;this more extensive guide&lt;/a&gt;. If you use keys other than &lt;code&gt;~.ssh/id_rsa.pub&lt;/code&gt; you also have to add an &lt;code&gt;IdentityFile&lt;/code&gt; entry to your &lt;code&gt;.ssh/config&lt;/code&gt; settings. Afterwards, you will be able to access the machine by simply doing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;ssh ec2-instance
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id=&#34;additional-tools&#34;&gt;Additional Tools&lt;/h1&gt;
&lt;p&gt;A great terminal gets you a long way. However, some additional tools can help make your workstation more complete. In this section, I try to present some of my favorites that could be useful to almost anyone.&lt;/p&gt;
&lt;h2 id=&#34;text-editing-vscodehttpscodevisualstudiocom&#34;&gt;Text Editing: &lt;a href=&#34;https://code.visualstudio.com/&#34;&gt;VSCode&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For most of my more extensive software engineering projects, I use JetBrains products or Eclipse. I will not describe setting up those in this post, as this would easily mandate a separate article. However, I present a more lightweight editing environment that is often a better choice than to start up a full-blown IDE. For a software engineer, especially for one that deals also with DevOps workloads, it is the daily business to edit and view all kinds of text-based files, such as .&lt;code&gt;txt&lt;/code&gt;, &lt;code&gt;.csv&lt;/code&gt;, &lt;code&gt;.md&lt;/code&gt;, &lt;code&gt;xml&lt;/code&gt;, &lt;code&gt;.yml&lt;/code&gt;, and &lt;code&gt;.json&lt;/code&gt;. For this, it is essential to have a fast text editor which can deal with large files and still provides sophisticated editing features.&lt;/p&gt;
&lt;p&gt;Previously, I used Sublime. For a couple of months now, I have switched to Visual Studio Code and I am still amazed by it. Comparing it to Sublime makes little sense in my opinion, because VSCode is just on a whole other level. Even for software projects with relatively simple technology stacks, such as this blog website, I now prefer VSCode over Eclipse or the JetBrains suite.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/5/vscode.png&#34;
         alt=&#34;Screenshot of VSCode showing the default light theme (Light&amp;#43;) (Source).&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Screenshot of VSCode showing the default light theme (Light+) (&lt;a href=&#34;https://superdevresources.com/best-vscode-light-themes/&#34;&gt;Source&lt;/a&gt;).&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The easiest way to install Visual Studio Code on Ubuntu is to visit the &lt;a href=&#34;https://code.visualstudio.com/&#34;&gt;official website&lt;/a&gt; and to download the Debian package. You could have probably figured out how to do this yourself, but do you also know how to set VSCode as the default application for all text files? Simply do&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;xdg-mime default code.desktop text/plain
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will spare you the headache of doing &lt;em&gt;Right-Click -&amp;gt; Properties -&amp;gt; Open With -&amp;gt; Set as default&lt;/em&gt; on every file type separately.&lt;/p&gt;
&lt;p&gt;Switching to VSCode from Sublime (or any other editor) is a surprisingly pleasant experience. VSCode will recommend the most relevant plugins as you go. For example, if you open a &lt;code&gt;.py&lt;/code&gt; file, some extensions for Python development will be recommended. Installing them is then just a click away and doesn&amp;rsquo;t even require a restart of the application. After some days of following VSCode&amp;rsquo;s recommendations and occasionally searching for some extensions manually, my setup was more complete and efficient than it ever was with Sublime.&lt;/p&gt;
&lt;h3 id=&#34;vscode-terminal-with-agnoster&#34;&gt;VSCode Terminal with Agnoster&lt;/h3&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;If you don&amp;rsquo;t intend to use the integrated VSCode terminal, you can safely skip this section.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;If you followed this guide until here and you now open an integrated Terminal in VSCode, you will likely be a bit disappointed. There are several problems. With our current setup, we need a monospace font, and the coloring does not work nicely with VSCode&amp;rsquo;s light theme.&lt;/p&gt;
&lt;p&gt;Fortunately, we can fix these things:&lt;/p&gt;
&lt;p&gt;First, install the &lt;a href=&#34;https://github.com/abertsch/Menlo-for-Powerline&#34;&gt;Menlo for Powerline&lt;/a&gt; font:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;git clone https://github.com/abertsch/Menlo-for-Powerline.git
sudo mv &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;Menlo for Powerline.ttf&amp;#34;&lt;/span&gt; /usr/share/fonts/
sudo fc-cache -vf /usr/share/fonts/ &lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# Refresh fonts cache&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, we change our terminal prompt colors a little bit to be compatible with both light and dark themes. Open &lt;code&gt;~/.oh-my-zsh/themes/agnoster.zsh-theme&lt;/code&gt; and change &lt;code&gt;prompt_context&lt;/code&gt; to the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# Context: user@hostname (who am I and where am I)&lt;/span&gt;
prompt_context&lt;span style=&#34;color:#555&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;{&lt;/span&gt;
  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;[[&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#033&#34;&gt;$USER&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt; !&lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#033&#34;&gt;$DEFAULT_USER&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;||&lt;/span&gt; -n &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#033&#34;&gt;$SSH_CLIENT&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;]]&lt;/span&gt;; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;then&lt;/span&gt;
    prompt_segment cyan default &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;%(!.%{%F{yellow}%}.)%n@%m&amp;#34;&lt;/span&gt;
  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;fi&lt;/span&gt;
&lt;span style=&#34;color:#555&#34;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, we apply the new font and the new colors in the VSCode settings. Press &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Shift&lt;/kbd&gt;+&lt;kbd&gt;P&lt;/kbd&gt; and start to type &lt;code&gt;Preferences: Open Settings (JSON)&lt;/code&gt;. We can now add the following lines to &lt;code&gt;settings.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;    &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;terminal.integrated.fontFamily&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;Menlo for Powerline&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;,&lt;/span&gt; 
    &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;workbench.colorCustomizations&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;:&lt;/span&gt; {
        &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;terminal.ansiBlue&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#01A0E4&amp;#34;&lt;/span&gt;,
        &lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;&amp;#34;terminal.ansiCyan&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;#B5E4F4&amp;#34;&lt;/span&gt;,
    }&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The integrated VSCode terminal now looks nice and has all the functionality of all of our Zsh plugins.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/5/vscode-terminal.png&#34;/&gt; 
&lt;/figure&gt;

&lt;h2 id=&#34;sync-configuration-files-mackuphttpsgithubcomlramackup&#34;&gt;Sync Configuration Files: &lt;a href=&#34;https://github.com/lra/mackup&#34;&gt;Mackup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So you just set up your desktop workstation, modified a bunch of configuration files, manually adjusted your VSCode keyboard shortcuts and now you want to do this also with your notebook?&lt;/p&gt;
&lt;p&gt;The solution to this is one of my favorite tools of all: &lt;a href=&#34;https://github.com/lra/mackup&#34;&gt;Mackup&lt;/a&gt;&lt;br&gt;
It will use any software that can sync a folder, such as Dropbox, to keep your application settings in sync.&lt;/p&gt;
&lt;p&gt;You can install it with &lt;code&gt;pip&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;pip install --upgrade mackup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;If you have versioning troubles with &lt;code&gt;pip&lt;/code&gt; or Python on Ubuntu 20.04, I recommend to do &lt;code&gt;sudo apt install python-is-python3&lt;/code&gt;. It will symlink &lt;code&gt;/usr/bin/python&lt;/code&gt; to &lt;code&gt;python3&lt;/code&gt; and hence make your transition to a world without Python 2 easier.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;Once Mackup is installed, you can copy your application settings to Dropbox (per default) doing&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;mackup backup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To apply them at a different machine, just use&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;mackup restore
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Behind the scenes, Mackup copies your configuration files to your Dropbox and then replaces the originals with symbolic links. Have a look at the &lt;a href=&#34;https://github.com/lra/mackup#bullsht-what-does-it-really-do-to-my-files&#34;&gt;GitHub Readme&lt;/a&gt; for more details.&lt;/p&gt;
&lt;h2 id=&#34;advanced-screenshots-shutterhttpslaunchpadnetshutter&#34;&gt;Advanced Screenshots: &lt;a href=&#34;https://launchpad.net/shutter&#34;&gt;Shutter&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you are coming from Windows, you probably use the Snipping Tool on a regular basis. With &lt;em&gt;Screenshot&lt;/em&gt;, Ubuntu has a similar tool installed by default. However, there is another option that is superior to both the Windows Snipping Tool and the Ubuntu default: &lt;a href=&#34;https://launchpad.net/shutter&#34;&gt;Shutter&lt;/a&gt;&lt;br&gt;
Unfortunately, it is no longer included in the default package repositories since Ubuntu 18.10. So, we have to add a third-party PPA first:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo add-apt-repository ppa:linuxuprising/shutter
sudo apt-get install shutter
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It is very simple to use and does not force you to become familiar with its advanced features. There are a few alternatives that are still actively developed, such as &lt;a href=&#34;https://github.com/flameshot-org/flameshot&#34;&gt;Flameshot&lt;/a&gt;. Unfortunately, those are missing a lot of Shutter&amp;rsquo;s more advanced features. So, I will stick with Shutter for as long as I can make it run on Ubuntu.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/5/shutter.png&#34;/&gt; 
&lt;/figure&gt;

&lt;h2 id=&#34;pick-colors-pickhttpswwwkryogenixorgcodepick&#34;&gt;Pick Colors: &lt;a href=&#34;https://www.kryogenix.org/code/pick/&#34;&gt;Pick&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you do front-end development, chances are you need a color picking tool on your computer. The most commonly used tool on Ubuntu is GPick. However, it drives me crazy. Usually, I just want to pick a color and get the hex code into my clipboard. GPick makes this very hard. So instead, I recommend &lt;a href=&#34;https://www.kryogenix.org/code/pick/&#34;&gt;Pick&lt;/a&gt;, a simple and functional alternative.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/5/pick.png&#34;/&gt; 
&lt;/figure&gt;

&lt;h2 id=&#34;cheatsheet-keycombiner-desktophttpskeycombinercomdesktop&#34;&gt;Cheatsheet: &lt;a href=&#34;https://keycombiner.com/desktop/&#34;&gt;KeyCombiner Desktop&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most people know KeyCombiner as a tool for browsing and learning shortcuts. However, its desktop app serves an additioanl purpose with its instant lookup feature: It is a context-aware cheatsheet that can show the shortcuts of the currently active application in addition to whatever key combinations and text snippets I have in my personal KeyCombiner collections.&lt;/p&gt;
&lt;p&gt;To trigger it, I only have to press &lt;kbd&gt;Super&lt;/kbd&gt;+&lt;kbd&gt;Alt&lt;/kbd&gt;+&lt;kbd&gt;C&lt;/kbd&gt;. The main advantage is that I don&amp;rsquo;t have to leave the current context and can resume work right after looking up the required piece of information:&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/keycombiner/lookup-three-apps2.gif&#34;
         alt=&#34;KeyCombiner&amp;amp;rsquo;s instant shortcut lookup for VSCode, Terminal, and Chrome.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;KeyCombiner&amp;rsquo;s instant shortcut lookup for VSCode, Terminal, and Chrome.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;communication-hub-ferdihttpsgetferdicom&#34;&gt;Communication Hub: &lt;a href=&#34;https://getferdi.com/&#34;&gt;Ferdi&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Did you ever see someone using their phone while sitting in front of their personal computer? Chances are, they were using a communication app that they could have used much more efficiently from their larger device with an actual keyboard. To get the benefit of receiving all notifications from all my communication tools, and to be able to respond easily, I use &lt;a href=&#34;https://getferdi.com/&#34;&gt;Ferdi&lt;/a&gt;. It is particularly useful if you have to monitor several Slack teams.&lt;/p&gt;
&lt;p&gt;In contrast to most of its competitors (Rambox, Wavebox, Franz 5), Ferdi is completely free and open-source.&lt;/p&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;If you want Ferdi or any other software that is distributed via an AppImage to show up properly in the dock, have a look at &lt;a href=&#34;https://github.com/AppImage/appimaged&#34;&gt;appimaged&lt;/a&gt;. It is open-source software that monitors specific directories for AppImage files and installs them properly in the system.&lt;/p&gt;

&lt;/div&gt;

&lt;div class=&#34;notices info&#34;&gt;
    &lt;p&gt;Previous versions of this article recommended &lt;a href=&#34;https://getstation.com/&#34;&gt;Station&lt;/a&gt; instead of Ferdi. However, Station pivoted to become a browser extension and is no longer an option for bundling communication channels outside of your regular browser.&lt;/p&gt;

&lt;/div&gt;
&lt;h2 id=&#34;record-short-screencasts-peekhttpsgithubcomphwpeek&#34;&gt;Record Short Screencasts: &lt;a href=&#34;https://github.com/phw/peek&#34;&gt;Peek&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Did you wonder how I created those fancy gifs showing my terminal? I thought so.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/phw/peek&#34;&gt;Peek&lt;/a&gt; is a very lightweight and simple tool to record short screencasts. To install it on Ubuntu do&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;sudo add-apt-repository ppa:peek-developers/stable
sudo apt update
sudo apt install peek
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/5/peek-recording-itself.gif&#34;
         alt=&#34;Peek in action. (Source).&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Peek in action. (&lt;a href=&#34;https://github.com/phw/peek&#34;&gt;Source&lt;/a&gt;).&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;During the creation of this article, I was made aware by a &lt;a href=&#34;https://knasmueller.net/&#34;&gt;good friend and fellow software engineer&lt;/a&gt; of dedicated terminal recording tools, such as &lt;a href=&#34;https://terminalizer.com/&#34;&gt;Terminalizer&lt;/a&gt; and &lt;a href=&#34;https://asciinema.org/&#34;&gt;asciinema&lt;/a&gt;. However, these require some initial setup work, especially if you want to show off a theme and require that your animated terminal looks exactly like your actual terminal.&lt;br&gt;
Since I already recorded most of the animations, I decided to stay with Peek for this post. Nevertheless, if your use case is terminal recording, you should probably go with one of the linked projects instead of Peek.&lt;/p&gt;
&lt;h2 id=&#34;mail-client-thunderbirdhttpswwwthunderbirdneten-us&#34;&gt;Mail Client: &lt;a href=&#34;https://www.thunderbird.net/en-US/&#34;&gt;Thunderbird&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For mail, I use &lt;a href=&#34;https://www.thunderbird.net/en-US/&#34;&gt;Thunderbird&lt;/a&gt;. I don&amp;rsquo;t particularly like it, but to my knowledge it is still the best desktop email client for Linux. Unfortunately, some minor design decisions make the program almost unusable for me in its default state. The reason I include this section about software that everyone knows already is mainly to highlight three changes I always make to the default setup:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;By default, Thunderbird sorts messages by date ascending. I think that&amp;rsquo;s an unfortunate default. Who wants to scroll down constantly to see the newest messages? To change the sort order to descending (newest first), go to &lt;em&gt;Preferences -&amp;gt; General -&amp;gt; Config Editor&lt;/em&gt; and set &lt;em&gt;mailnews.default_sort_order&lt;/em&gt; to value 2. See &lt;a href=&#34;https://superuser.com/a/13551/573438&#34;&gt;this StackExchange answer&lt;/a&gt; for more details about default sorting orders and other options.&lt;/li&gt;
&lt;li&gt;Install the &lt;a href=&#34;https://addons.thunderbird.net/en-US/thunderbird/addon/no-message-pane-sort-by-mouse/&#34;&gt;No Message Pane Sort by Mouse&lt;/a&gt; add-on.
With this extension, a click on the mail list table header will no longer change the defined sort order. Without it, misclicks while trying to select the first mail in the list are very annoying. Personally, I never want to change the sort order at all. However, it is still possible by holding &lt;kbd&gt;Ctrl&lt;/kbd&gt; while clicking.&lt;/li&gt;
&lt;li&gt;Finally, go to &lt;em&gt;Preferences -&amp;gt; Composition&lt;/em&gt; and uncheck the &lt;em&gt;Use Paragraph format instead of Body Text by default&lt;/em&gt; to prevent Thunderbird       from adding absurd amounts of whitespace whenever you press the Enter key while composing a mail.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;notices tip&#34;&gt;
    &lt;p&gt;Are you already using Thunderbird on another computer? Fortunately, there is an easy way to migrate all your mail accounts and settings. Essentially, you can copy a hidden folder where Thunderbird stores all its data and move it to the new machine, &lt;a href=&#34;https://support.mozilla.org/en-US/kb/moving-thunderbird-data-to-a-new-computer&#34;&gt;as described in Mozilla&amp;rsquo;s official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;p&gt;If you know of a new fancy alternative to Thunderbird that handles parallel usage of several mail accounts well, please let me know.&lt;/p&gt;
&lt;h1 id=&#34;bonus-tip&#34;&gt;Bonus Tip&lt;/h1&gt;
&lt;p&gt;If you have read this far, I am willing to share my most secret Linux hack with you. Joking aside, it is very helpful to know what to do when your Linux workstation freezes. For some reason, I did not know until a couple years ago. Maybe the following trick can help you to avoid some forced restarts in the future.&lt;/p&gt;
&lt;p&gt;The most flexible way to re-gain control of a system that has frozen completely is pressing &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Alt&lt;/kbd&gt;+&lt;kbd&gt;F3&lt;/kbd&gt;. Then, use top and other process monitoring tools to find the process IDs of the faulty processes. Then, simply kill them (e.g. type &lt;code&gt;kill &amp;lt;pid&amp;gt;&lt;/code&gt;, &lt;code&gt;killall&lt;/code&gt;, or &lt;code&gt;kill -9&lt;/code&gt;). Afterward, you can get back to your work using &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;Alt&lt;/kbd&gt;+&lt;kbd&gt;F1&lt;/kbd&gt;. (&lt;a href=&#34;https://askubuntu.com/questions/547290/how-do-i-get-out-of-ctrl-alt-f3&#34;&gt;Source&lt;/a&gt;).&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I hope this post showed you some tools or practices that you didn&amp;rsquo;t know yet. I have had a good experience with all of them. Especially switching from Bash to Zsh and installing &lt;a href=&#34;http://localhost:1313/posts/setting-up-linux-machine/#installing-zsh-plugins&#34;&gt;the described plugins&lt;/a&gt; came with a real productivity boost. If you feel that I left out something important, I&amp;rsquo;d be very interested to hear your suggestions.&lt;/p&gt;
&lt;p&gt;If you are not mainly using Linux yet, the length of this post might give the impression that it is still a pain to set up a proper Linux workstation. However, you do not necessarily need all of the things I showed here. Also, everything covered in this post is very easy to install with minimal configuration overhead. I strongly believe that you would have a much harder time setting up a comparable Windows development workstation.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://imgs.xkcd.com/comics/cautionary.png&#34;
         alt=&#34;You can still spend your life setting up Linux. However, if you use a popular, well-supported distribution you definitely don&amp;amp;rsquo;t have to. (Source: xkcd.com)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;You can still spend your life setting up Linux. However, if you use a popular, well-supported distribution you definitely don&amp;rsquo;t have to. (Source: xkcd.com)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

</description>
    </item>
    
    <item>
      <title>Using Hugo, GitLab Pages, and Cloudflare to create and run this Website</title>
      <link>https://tkainrad.dev/posts/using-hugo-gitlab-pages-and-cloudflare-to-create-and-run-this-website/</link>
      <pubDate>Sat, 01 Jun 2019 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/using-hugo-gitlab-pages-and-cloudflare-to-create-and-run-this-website/</guid>
      <description>&lt;p&gt;In this post, I outline how I develop and run this blog site. I briefly explain my technology choices and give detailed instructions about how to use the resulting stack to create such a project. If you stick with me until the end, you will know how to run a beautiful, easily customizable static website for free.&lt;/p&gt;
&lt;p&gt;When I decided to start this blog for the reasons outlined in &lt;a href=&#34;../writing-a-swe-blog/&#34;&gt;my first post&lt;/a&gt;, I was already familiar with GitLab Pages, and the &lt;a href=&#34;https://www.mkdocs.org/&#34;&gt;MkDocs&lt;/a&gt; static site generator. I am using this combination for creating documentation websites, and it is working very well.&lt;/p&gt;
&lt;p&gt;Therefore, it was an easy choice for me to also rely on GitLab Pages for this site. I will cover its features in a &lt;a href=&#34;#hosting-with-gitlab-pages&#34;&gt;later section&lt;/a&gt; of this post. With hosting already covered, I just needed to create the actual site.&lt;/p&gt;
&lt;h1 id=&#34;creating-the-site-with-hugohttpsgohugoio&#34;&gt;Creating the site with &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Before starting this blog, the only static site generator I used in production environments was MkDocs, which is intended for documentation and clearly not a good choice for creating a personal blog site.&lt;/p&gt;
&lt;p&gt;A good overview of more appropriate alternatives is given on &lt;a href=&#34;https://www.staticgen.com/&#34;&gt;staticgen.com&lt;/a&gt;. For my purpose, probably all the popular projects would have been good choices. I decided to go with Hugo for two reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It is written in Go, a language with a promising future that I wanted to try out for a while already.&lt;/li&gt;
&lt;li&gt;There is a very well-curated list of open-source themes directly &lt;a href=&#34;https://themes.gohugo.io/&#34;&gt;on the official website&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So let&amp;rsquo;s get to work.&lt;/p&gt;
&lt;p&gt;To get started on Ubuntu, we can execute just two commands to install hugo and create a new project:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# install hugo&lt;/span&gt;
sudo snap install hugo

&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# create a new project&lt;/span&gt;
hugo new site personal-blog
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you are not using Ubuntu, your system&amp;rsquo;s package manager likely provides a similarly easy experience. Refer to &lt;a href=&#34;https://gohugo.io/getting-started/installing&#34;&gt;the official documentation&lt;/a&gt; for installation instructions for other operating systems.&lt;/p&gt;
&lt;p&gt;Before we start to work on our site, we want to install a theme:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# install the theme&lt;/span&gt;
&lt;span style=&#34;color:#366&#34;&gt;cd&lt;/span&gt; personal-blog
git init
git submodule add https://github.com/luizdepra/hugo-coder.git themes/hugo-coder
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I like that Hugo is very forward about using &lt;a href=&#34;https://git-scm.com/book/en/v2/Git-Tools-Submodules&#34;&gt;GIT submodules&lt;/a&gt; to install themes. This makes it easy to apply changes to the theme while keeping the connection to the original theme project. In an upcoming blog post, I will detail which changes I made to the theme itself. For now, we are perfectly fine with the default &lt;a href=&#34;https://github.com/luizdepra/hugo-coder/&#34;&gt;Hugo-Coder&lt;/a&gt; theme.&lt;/p&gt;
&lt;p&gt;We only need to apply some basic configuration. Simply replace the content of &lt;code&gt;config.toml&lt;/code&gt; with the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;21
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;baseurl = &amp;#34;http://www.example.com&amp;#34;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;title = &amp;#34;johndoe&amp;#34;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;theme = &amp;#34;hugo-coder&amp;#34;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;[params]&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;author = &amp;#34;John Doe&amp;#34;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;description = &amp;#34;John Doe&amp;#39;s personal website&amp;#34;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;keywords = &amp;#34;blog,developer,personal&amp;#34;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;info = &amp;#34;Full Stack Magician&amp;#34;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;avatarurl = &amp;#34;images/avatar.jpg&amp;#34;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;footercontent = &amp;#34;Enter a text here.&amp;#34;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;[taxonomies]&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;series = &amp;#34;series&amp;#34;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#09f;font-style:italic&#34;&gt;# Menu links&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;[[menu.main]]&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;name = &amp;#34;Blog&amp;#34;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;weight = 1&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;url  = &amp;#34;/posts/&amp;#34;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then, we create the first post:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;hugo new posts/my-first-post.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For the theme to properly recognize the new blog post, we have to replace the content of the newly created &lt;code&gt;/content/posts/my-first-post.md&lt;/code&gt; with the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;6
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;7
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;+++&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;date = &amp;#34;2019-01-01&amp;#34;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;title = &amp;#34;My first Post&amp;#34;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;slug = &amp;#34;my-first-post&amp;#34; &lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;tags = []&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;categories = []&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;series = []&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;+++&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Finally, we can get a first look at our new personal portfolio and blogging site, by starting up the Hugo development server. Simply type&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;hugo server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then go to &lt;a href=&#34;http://localhost:1313/&#34;&gt;http://localhost:1313/&lt;/a&gt; to see your new site:&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/hugo-coder-default-page.png&#34;
         alt=&#34;Hugo coder default page.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Hugo coder default page.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Testing your site locally is great, but showing the whole world that you are &lt;em&gt;John Doe, the Full Stack Magician&lt;/em&gt; is something else entirely. Luckily, we are very close to doing just that.&lt;/p&gt;
&lt;h1 id=&#34;hosting-with-gitlab-pageshttpsdocsgitlabcomeeuserprojectpages&#34;&gt;Hosting with &lt;a href=&#34;https://docs.gitlab.com/ee/user/project/pages/&#34;&gt;GitLab Pages&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;GitLab Pages is similar to GitHub Pages.
Both allow free hosting of static websites. GitLab Pages enables you to build the hosted static content via GitLab&amp;rsquo;s CI/CD features. This means that your site will automatically be rebuilt on every commit to the repository. Until the release of GitHub Actions, GitHub could not do this. Instead, you had to build the site locally and commit the static files to the repository. Nowadays, both GitLab and GitHub are perfectly fine choices to host your static site.&lt;/p&gt;
&lt;p&gt;Naturally, we start by creating a new repository.  Before pushing anything to this repository, you might want to perform some usual GIT maintenance tasks, such as creating a &lt;code&gt;.gitignore&lt;/code&gt; file. If you do not know it yet, you should have a look at the &lt;a href=&#34;https://www.toptal.com/developers/gitignore&#34;&gt;gitignore.io service&lt;/a&gt; service. The site generates &lt;code&gt;.gitignore&lt;/code&gt; files depending on your technology stack. For Hugo, it will for example exclude the &lt;code&gt;/public/&lt;/code&gt; folder, which is very much what we want, because we will build this folder via a GitLab CI pipeline on every push to the repository.&lt;/p&gt;
&lt;p&gt;Once everything is in order, we can simply follow GitLab&amp;rsquo;s instructions for pushing our existing project folder:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-Bash&#34; data-lang=&#34;Bash&#34;&gt;&lt;span style=&#34;color:#366&#34;&gt;cd&lt;/span&gt; personal-blog
git remote add origin git@gitlab.com:&amp;lt;path-to-your-repository&amp;gt;
git add .
git commit -m &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;Initial commit&amp;#34;&lt;/span&gt;
git push -u origin master&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Finally, we get to the exciting part: Having our site built and published on every new commit to the repository. If you are not yet familiar with GitLab&amp;rsquo;s CI/CD features, check out &lt;a href=&#34;https://docs.gitlab.com/ee/ci/&#34;&gt;their docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Fortunately, getting started is very simple. Simply put a file called &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; in the root of your project. This YAML configuration file holds all CI/CD configuration. For our Hugo-powered blog, the following file content is sufficient:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;image&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;monachus/hugo&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;pages&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;script&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;- hugo&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;artifacts&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;paths&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;- public&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#309;font-weight:bold&#34;&gt;only&lt;/span&gt;:&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;- master&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The first line specifies the docker base image to use for our pipeline. For this brief tutorial, we will not use any testing stages, instead, we have just one simple job called &lt;code&gt;pages&lt;/code&gt;. It executes the &lt;code&gt;hugo&lt;/code&gt; command, which will build the static site content in the &lt;code&gt;public&lt;/code&gt; directory. Somewhat incidentally, this is also where GitLab Pages expects to find our static site content. The only thing left to do is publishing the &lt;code&gt;public&lt;/code&gt; directory as &lt;a href=&#34;https://docs.gitlab.com/ee/user/project/pipelines/job_artifacts.html&#34;&gt;a job artifact&lt;/a&gt;. The last two lines are optional and state that the site should only be built and published on commits to the &lt;code&gt;master&lt;/code&gt; branch.&lt;/p&gt;
&lt;p&gt;Once you push this file to your repository&amp;rsquo;s master branch, the static site will be built and published within a few minutes. Go to &lt;em&gt;Settings-&amp;gt;Pages&lt;/em&gt; to verify that everything is in order and to see the default URL of your new website. If you own a domain you would like to use rather than the default *.gitlab.io one, you can head &lt;a href=&#34;https://docs.gitlab.com/ee/user/project/pages/getting_started_part_three.html&#34;&gt;to the docs&lt;/a&gt; or check out this &lt;a href=&#34;https://about.gitlab.com/2016/04/07/gitlab-pages-setup/&#34;&gt;more detailed tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, before you do this, you may want to read the next section of this blog post, as the information given there could spare you some headaches.&lt;/p&gt;
&lt;h1 id=&#34;speeding-up-load-times-and-configuring-url-redirections-with-cloudflarehttpswwwcloudflarecom&#34;&gt;Speeding up load times and configuring URL redirections with &lt;a href=&#34;https://www.cloudflare.com/&#34;&gt;Cloudflare&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Initially, I configured SSL for this site via Let&amp;rsquo;s Encrypt. In principle, this is a perfectly fine way to do it. There is a &lt;a href=&#34;https://docs.gitlab.com/ee/user/project/pages/lets_encrypt_for_gitlab_pages.html&#34;&gt;good tutorial in the official GitLab Docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, it is a little bit tedious, as it requires frequent renewals, a process that can not be automated easily. Furthermore, I had problems redirecting from www. to non-www URLs with Namecheap, which is my domain name registrar. The issue was complicated by the fact that this site uses a .dev domain which is by default on the &lt;a href=&#34;https://opensource.google.com/projects/hstspreload&#34;&gt;HSTS preload list&lt;/a&gt;. Namecheap seems to prefer selling their own SSL certificates rather than fully support and document working with Let&amp;rsquo;s Encrypt.&lt;/p&gt;
&lt;p&gt;I solved all those issues at once by simply signing up to Cloudflare. The free version is perfectly fine for me. After signing up, Cloudflare provides very simple and precise instructions for getting started. Basically, you have to configure your domain to use their nameservers instead of the ones provided by your original domain name registrar.&lt;/p&gt;
&lt;p&gt;Once your site is configured to run with Cloudflare, you can use their &lt;em&gt;Page Rules&lt;/em&gt; feature to easily redirect traffic from the www. subdomain to your canonical non-www URL or the other way around.&lt;/p&gt;
&lt;p&gt;Furthermore, Cloudflare provides SSL encryption out of the box from their servers to your clients. All your static sites hosted with GitLab Pages and served via Cloudflare will be accessible over HTTPS without any additional configuration. Of course, we also want to encrypt the traffic from Cloudflare to the origin GitLab Pages host. For this, Cloudflare offers &lt;em&gt;Origin Certificates&lt;/em&gt;. I will not explain this further, as there is an excellent &lt;a href=&#34;https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/&#34;&gt;tutorial on this provided directly by GitLab&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt;
&lt;em&gt;&lt;a href=&#34;https://about.gitlab.com/2019/07/22/gitlab-12-1-released/&#34;&gt;With GitLab 12.1&lt;/a&gt;, released on July 22, 2019, it is possible to automatically get HTTPS certificates for GitLab Pages using Let’s Encrypt. Using Cloudflare Origin Certificates is still a perfectly fine approach, however, it is no longer more convenient than just using GitLab&amp;rsquo;s Let&amp;rsquo;s Encrypt integration.&lt;/em&gt;&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The biggest advantage of this setup is its financial cost. Apart from buying the &lt;a href=&#34;http://tkainrad.dev&#34;&gt;tkainrad.dev&lt;/a&gt; domain, creating this site did not cost any money. Furthermore, the page is now running at absolutely zero cost. This is not restricted to a limited timeframe, thanks to GitLab Pages.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://imgs.xkcd.com/comics/reduce_your_payments.png&#34;
         alt=&#34;Fortunately, we do not have to resort to chemistry puns to reduce our hosting bills. (Source: xkcd.com)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Fortunately, we do not have to resort to &lt;a href=&#34;https://www.explainxkcd.com/wiki/index.php/1426:_Reduce_Your_Payments&#34;&gt;chemistry puns&lt;/a&gt; to reduce our hosting bills. (Source: xkcd.com)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If you have basic programming skills, I highly recommend hosting your static sites with this free service instead of paying a monthly fee to a hosting provider.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>High-performance Computing with AWS Parallelcluster</title>
      <link>https://tkainrad.dev/posts/aws-parallelcluster/</link>
      <pubDate>Tue, 07 May 2019 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/aws-parallelcluster/</guid>
      <description>&lt;h1 id=&#34;overview&#34;&gt;Overview&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;AWS ParallelCluster&lt;/em&gt; is a toolkit for automating the process of building, configuring,
and managing clusters of virtual machines on the Amazon Elastic Compute Cloud (EC2)
cloud. These clusters can be used similar to traditional HPC clusters, as illustrated below.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/drms.svg&#34;
         alt=&#34;Operating principle of a traditional resource-manager based computing cluster.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Operating principle of a traditional resource-manager based computing cluster.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The software is essentially a command-line tool written in Python that
provides simple commands for creating, updating, stopping, starting, and deleting HPC
clusters in the AWS EC2 cloud. Naturally, to provide these functionalities, the locally
installed CLI has to use the web APIs of existing AWS services. The most important
one of these services is AWS Cloudformation. It facilitates the creation
and management of a collection of related AWS resources. A set of nodes belonging to a
specific HPC cluster can be such a collection. Another essential service is Auto Scaling,
providing dynamic elastic scaling of the number of compute nodes depending on the
current workload. As a consequence, ParallelCluster can be configured to automatically add
nodes when jobs are pending and not able to run due to the cluster being fully utilized.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/elasticity.svg&#34;
         alt=&#34;Cloud elasticity allows automated addition and deletion of nodes depending on the current job queue size.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Cloud elasticity allows automated addition and deletion of nodes depending on the current job queue size.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;ParallelCluster is maintained directly by Amazon Web Services (AWS) and developed
as an open source project &lt;a href=&#34;https://github.com/aws/aws-parallelcluster&#34;&gt;accessible on GitHub&lt;/a&gt;. While the restriction to AWS is
certainly disadvantageous, the official support from and for a specific cloud provider
results in a stable and regularly updated product. The latter is especially important
for any framework facilitating cluster computing in the cloud, because the tool has to
be able to deal with new versions of operating systems, updates to cluster computing
software and changes in the product palette of the supported cloud providers.&lt;/p&gt;
&lt;h1 id=&#34;performance-compared-to-on-site-clusters&#34;&gt;Performance Compared to On-Site Clusters&lt;/h1&gt;
&lt;p&gt;HPC in the cloud faces complex challenges that have to be overcome in order to compete
with on-site clusters. Specifically, these include the interconnection
networks between machines in the cloud datacenters, which are most often not built for supporting HPC applications, and secondly, a performance decrease due to virtualization.&lt;/p&gt;
&lt;p&gt;Already in 2009, &lt;a href=&#34;https://doi.org/10.1145/1531666.1531671&#34;&gt;Napper et al.&lt;/a&gt; have used the &lt;a href=&#34;https://doi.org/10.1002/cpe.728&#34;&gt;LINPACK benchmark&lt;/a&gt; to assess
the performance of HPC clusters allocated in the Amazon EC2 cloud. The sobering
conclusion has been that, at least for communication intensive problems, HPC in the
cloud can not yet make use of its potential strengths regarding scalability and cost saving.
To the contrary, it has been shown that the achieved floating point operations per second
(FLOPS) per dollar spent decrease exponentially as the allocated cluster grows. This is,
as expected, due to the slow interconnection network and limited memory available on
single nodes.
One year later, in 2010, &lt;a href=&#34;https://doi.org/10.1109/CloudCom.2010.69&#34;&gt;Jackson et al.&lt;/a&gt; have performed similar experiments and also
encountered the familiar problem of slow network connections. They observed a clear
correlation between the runtime of different distributed applications and the amount
of time spent communicating. However, Jackson et al. have also shown that for
applications requiring little communication between computing nodes, HPC cloud clusters
can scale well.&lt;/p&gt;
&lt;p&gt;The situation has since progressed further. Real world problems are often embarrassingly
parallel or come close to it, enabling all benefits of cloud computing, even with sub-par
interconnection networks. This development is illustrated by various case studies of
different scientific fields, especially the computational life sciences. &lt;a href=&#34;https://aws.amazon.com/solutions/case-studies/novartis/&#34;&gt;Novartis used the Amazon EC2 cloud&lt;/a&gt; with 87 000 on-demand CPU cores to vastly decrease computation
time for a VS experiment, a scientific technique explained in detail in Section 2.5.
&lt;a href=&#34;https://cyclecomputing.com/hgst-buys-70000-core-cloud-hpc-cluster-breaks-record-8-hours/&#34;&gt;HGST built a similarly strong cloud cluster&lt;/a&gt; of 70 000 cores to run a simulation for finding
an optimal hard drive head design.&lt;/p&gt;
&lt;h1 id=&#34;getting-started&#34;&gt;Getting Started&lt;/h1&gt;
&lt;p&gt;Perhaps the most important benefit of using a cloud cluster is the possibility to set it up in a couple of minutes. Provisioning a physical HPC cluster requires competent system administrators, air conditioned space for the machines, a large up-front hardware investement, etc. etc.&lt;/p&gt;
&lt;p&gt;Getting started with AWS Parallelcluster only requires an AWS account and the ParallelCluster software. Detailed instructions for creating an account can be found at the &lt;a href=&#34;https://docs.inteligand.com/ls-remote/working-with-aws/#creating-your-aws-account&#34;&gt;official AWS website&lt;/a&gt;. The installation procedure for ParallelCluster is also well described at &lt;a href=&#34;https://aws-parallelcluster.readthedocs.io/en/latest/getting_started.html&#34;&gt;the official docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before you can configure ParallelCluster, it is need to create an AWS Access Key. To do this, click on your Account name in the top right of the AWS management console browser application. There is an option &lt;em&gt;My Security Credentials&lt;/em&gt;. Additionally, you should create an AWS EC2 Key Pair via &lt;em&gt;Services -&amp;gt; EC2 -&amp;gt; Key Pairs -&amp;gt; Create Key Pair&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Finally, you can set up your local ParallelCluster installation. Simply type&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;pcluster configure
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;in a terminal window. ParallelCluster will ask for the access key you just created. This automated configuration process will further be able to retrieve default values for &lt;code&gt;master_subnet_id&lt;/code&gt; and &lt;code&gt;vpc_id&lt;/code&gt;. These are otherwise a little tedious to find out. In general, you can just press enter on all questions except &lt;code&gt;aws_access_key_id&lt;/code&gt; and &lt;code&gt;aws_secret_access_key&lt;/code&gt;. More sophisticated configuration can easily be applied later by directly modifying the file &lt;code&gt;~/.parallelcluster/config&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The following is a basic configuration for an elastic cluster with one to five compute nodes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;21
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;22
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;23
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;24
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;25
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;26
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;27
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;28
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-PacmanConf&#34; data-lang=&#34;PacmanConf&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;[aws]&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;aws_region_name&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; eu-central-1&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;aws_access_key_id&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &amp;lt;AWS_ACCESS_KEY_ID&amp;gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;aws_secret_access_key&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &amp;lt;AWS_SECRET_ACCESS_KEY&amp;gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;[cluster default]&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;vpc_settings&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; public&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;key_name&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &amp;lt;AWS_KEY_NAME&amp;gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;ebs_settings&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; custom&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;post_install&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &amp;lt;POST_INSTALL_SCRIPT_DOWNLOAD_PATH&amp;gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;base_os&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; ubuntu1604&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;maintain_initial_size&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; true&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;compute_instance_type&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; c4.2xlarge&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;initial_queue_size&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; 1&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;max_queue_size&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; 5&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;[vpc public]&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;master_subnet_id&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &amp;lt;AWS_MASTER_SUBNET_ID&amp;gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;vpc_id&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &amp;lt;AWS_VPC_ID&amp;gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;[global]&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;update_check&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; true&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;sanity_check&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; true&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;cluster_template&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; default&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;[ebs custom]&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;ebs_snapshot_id&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &amp;lt;SNAPSHOT_ID&amp;gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;
&lt;/span&gt;&lt;span style=&#34;color:#a00;background-color:#faa&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;volume_type&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; gp2&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Once your configuration is finished, you can create your first cloud cluster with a simple:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;pcluster create &amp;lt;cluster-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Naturally, for working with AWS and ParallelCluster, you will want to also look at the official documentation. In the following I list some of the most important resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/aws/aws-parallelcluster&#34;&gt;AWS ParallelCluster GitHub page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://aws-parallelcluster.readthedocs.io/en/latest/&#34;&gt;AWS ParallelCluster documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://aws.amazon.com/ec2/pricing/on-demand/&#34;&gt;Amazon EC2 on-demand instance pricing list&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://aws.amazon.com/ebs/pricing/&#34;&gt;Amazon EBS pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://aws.amazon.com/aws-cost-management/&#34;&gt;General documentation for managing costs with AWS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;p&gt;Parts of this blog post have been taken from my thesis on &lt;em&gt;Providing Transparent Remote Access to HPC Resources for Graphical Desktop Applications&lt;/em&gt;. If you are further interested in working with AWS Parallelcluster you can freely download it &lt;a href=&#34;https://www.dropbox.com/s/424pyrixr1vn6xz/Kainrad_Diplomarbeit_Thesis.pdf?dl=0&#34;&gt;here&lt;/a&gt;. For a brief description of an in-production AWS Parallelcluster use case, check out the &lt;a href=&#34;https://www.doi.org/10.1021/acs.jcim.8b00716&#34;&gt;journal article&lt;/a&gt; that has emerged from the thesis.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Nitrite as embedded NoSQL Database for Java</title>
      <link>https://tkainrad.dev/posts/nitrite-embedded-nosql-db/</link>
      <pubDate>Mon, 06 May 2019 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/nitrite-embedded-nosql-db/</guid>
      <description>&lt;h1 id=&#34;problem-description&#34;&gt;Problem Description&lt;/h1&gt;
&lt;p&gt;Recently, I had the exciting challenge to redesign the database representation of a potentially very large list of elements, each with an arbitrary number of possibly different attributes. Previously, the list was internally stored in an embedded SQL database. In order to deal with the fixed schema requirements of SQL, the following schema was conceptualized:&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/er-diagram.svg&#34;
         alt=&#34;ER-Diagram for handling elements with different attributes in relational databases.&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;ER-Diagram for handling elements with different attributes in relational databases.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This approach is perfectly fine for a few thousand elements. However, when performance becomes critical because you would like to support very large databases, this approach reaches its limitations. This is not surprising because the relational database needs to perform &lt;em&gt;JOIN&lt;/em&gt; operations for almost all use cases. Simply displaying an element with all its properties requires a &lt;em&gt;JOIN&lt;/em&gt; of all three tables.&lt;/p&gt;
&lt;p&gt;Nevertheless, it is a very common approach with relational databases. I cannot think of many ways to improve this schema without introducing other pitfalls. I would be very interested to hear about improved schema suggestions in the comments below.&lt;/p&gt;
&lt;p&gt;However, there is definitely one way to improve this situation:&lt;br&gt;
&lt;strong&gt;Getting rid of the relational database completely.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Having a list of elements that can each have an arbitrary number of properties which might differ from each other is a perfect use case for a document-oriented NoSQL database. The only challenge remaining was to find an embeddable open-source project, as those were strict requirements.&lt;/p&gt;
&lt;h1 id=&#34;nitrite-overview&#34;&gt;Nitrite Overview&lt;/h1&gt;
&lt;p&gt;When I started to look for NoSQL alternatives to tackle the described problem, I immediately thought of MongoDB. I have used it already in some small projects and feel quite comfortable with it. Unfortunately, there is no embedded version of MongoDB, &lt;a href=&#34;https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo&#34;&gt;except for testing&lt;/a&gt; and &lt;a href=&#34;https://www.mongodb.com/products/mobile?lang=de-de&#34;&gt;for mobile app development&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So my search went on. At first, I stumbled upon some very interesting key-value store projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chronicle Map: &lt;a href=&#34;https://github.com/OpenHFT/Chronicle-Map&#34;&gt;https://github.com/OpenHFT/Chronicle-Map&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;MapDB: &lt;a href=&#34;https://github.com/jankotek/mapdb/&#34;&gt;https://github.com/jankotek/mapdb/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I am happy to have these on my radar now, they are certainly very interesting and might find some use in future projects. However, for the above described use case, a key-value store is not a good fit. Instead, a document-oriented approach similar to MongoDB would be ideal. With this requirement, I actually had trouble finding open source projects. In the end, I had two relatively unknown alternatives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jsondb: &lt;a href=&#34;https://github.com/Jsondb/jsondb-core&#34;&gt;https://github.com/Jsondb/jsondb-core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nitrite Database: &lt;a href=&#34;https://github.com/dizitart/nitrite-database&#34;&gt;https://github.com/dizitart/nitrite-database&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The decision in favor of Nitrite was quite obvious, it has &lt;a href=&#34;https://www.dizitart.org/nitrite-database/&#34;&gt;beautiful and extensive documentation&lt;/a&gt; and boasts a &lt;em&gt;&amp;ldquo;Very fast and lightweight MongoDB like API&amp;rdquo;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/dizitart/nitrite-database&#34;&gt;&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://tkainrad.dev/images/nitrite-logo.svg&#34;
         alt=&#34;Nitrite Database logo.&#34; width=&#34;150&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Nitrite Database logo.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&#34;implementation&#34;&gt;Implementation&lt;/h1&gt;
&lt;p&gt;Implementing our use case in a document-oriented NoSQL database is actually very straightforward. We simply create a &lt;code&gt;NitriteCollection&lt;/code&gt; and add all our elements as &lt;code&gt;Document&lt;/code&gt; to this collection. Each document will contain exactly the attributes that the respective element actually has set. This requires much less effort than with fixed-schema databases.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-Java&#34; data-lang=&#34;Java&#34;&gt;Nitrite db &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; Nitrite&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;builder&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;()&lt;/span&gt;
    &lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;filePath&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;databaseFile.db&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;).&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;openOrCreate&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;user&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;);&lt;/span&gt;

NitriteCollection collection &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; db&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;getCollection&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;elementCollection&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;);&lt;/span&gt;

&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; 0&lt;span style=&#34;color:#555&#34;&gt;;&lt;/span&gt;
&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;Element element &lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; elements&lt;span style=&#34;color:#555&#34;&gt;)&lt;/span&gt;
&lt;span style=&#34;color:#555&#34;&gt;{&lt;/span&gt;
  Document document &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; Document&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;createDocument&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;tableId&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt; i&lt;span style=&#34;color:#555&#34;&gt;);&lt;/span&gt;
  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;Map&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;Entry&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt;String&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt; String&lt;span style=&#34;color:#555&#34;&gt;&amp;gt;&lt;/span&gt; entry &lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; map&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;entrySet&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;()))&lt;/span&gt;
  &lt;span style=&#34;color:#555&#34;&gt;{&lt;/span&gt;
    document&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;put&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;entry&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;getKey&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(),&lt;/span&gt; entry&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;getValue&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;());&lt;/span&gt;
  &lt;span style=&#34;color:#555&#34;&gt;}&lt;/span&gt;
  collection&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;insert&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;document&lt;span style=&#34;color:#555&#34;&gt;);&lt;/span&gt;
  i&lt;span style=&#34;color:#555&#34;&gt;++&lt;/span&gt;
&lt;span style=&#34;color:#555&#34;&gt;}&lt;/span&gt;
collection&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;createIndex&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;tableId&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt; IndexOptions&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;indexOptions&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;IndexType&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;Unique&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;true&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;));&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;tableId&lt;/code&gt; attribute is specific to my use case. It allows to index my elements for later use with a &lt;code&gt;JTable&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id=&#34;using-nitrite-as-jtable-backend&#34;&gt;Using Nitrite as JTable Backend&lt;/h1&gt;
&lt;p&gt;I did not (yet) create a proper, reproducible evaluation setup. However, it is quite easy to get a feeling for how fast &lt;code&gt;Nitrite&lt;/code&gt; compares to embedded SQL solutions (Derby, H2, &amp;hellip;).
Simply create a a basic Java Swing GUI containing a JTable with the following &lt;code&gt;TableModel&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;21
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;22
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;23
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;24
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;25
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;26
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;27
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;28
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;29
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;30
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;31
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;32
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;33
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;34
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;35
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;36
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;37
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;38
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;39
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;40
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;41
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;42
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;43
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;44
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;45
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;46
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;47
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;48
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;49
&lt;/span&gt;&lt;span style=&#34;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;50
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-Java&#34; data-lang=&#34;Java&#34;&gt;&lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#0a8;font-weight:bold&#34;&gt;NitriteTableModel&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;extends&lt;/span&gt; AbstractTableModel
&lt;span style=&#34;color:#555&#34;&gt;{&lt;/span&gt;
  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;private&lt;/span&gt; NitriteCollection nitriteCollection&lt;span style=&#34;color:#555&#34;&gt;;&lt;/span&gt;
  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;private&lt;/span&gt; List&lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt;String&lt;span style=&#34;color:#555&#34;&gt;&amp;gt;&lt;/span&gt; attributeNames&lt;span style=&#34;color:#555&#34;&gt;;&lt;/span&gt;
  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;final&lt;/span&gt; List&lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt;Document&lt;span style=&#34;color:#555&#34;&gt;&amp;gt;&lt;/span&gt; filteredDocuments &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;new&lt;/span&gt; ArrayList&lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt;Document&lt;span style=&#34;color:#555&#34;&gt;&amp;gt;();&lt;/span&gt;


  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;NitriteTableModel&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;NitriteCollection nitriteCollection&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt;
      List&lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt;String&lt;span style=&#34;color:#555&#34;&gt;&amp;gt;&lt;/span&gt; attributeNames&lt;span style=&#34;color:#555&#34;&gt;)&lt;/span&gt;
  &lt;span style=&#34;color:#555&#34;&gt;{&lt;/span&gt;
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;super&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;();&lt;/span&gt;
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;this&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;nitriteCollection&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; nitriteCollection&lt;span style=&#34;color:#555&#34;&gt;;&lt;/span&gt;
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;this&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;attributeNames&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; attributeNames&lt;span style=&#34;color:#555&#34;&gt;;&lt;/span&gt;
  &lt;span style=&#34;color:#555&#34;&gt;}&lt;/span&gt;

  &lt;span style=&#34;color:#99f&#34;&gt;@Override&lt;/span&gt;
  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;getColumnCount&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;()&lt;/span&gt;
  &lt;span style=&#34;color:#555&#34;&gt;{&lt;/span&gt;
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; attributeNames&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;size&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;();&lt;/span&gt;
  &lt;span style=&#34;color:#555&#34;&gt;}&lt;/span&gt;

  &lt;span style=&#34;color:#99f&#34;&gt;@Override&lt;/span&gt;
  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;getRowCount&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;()&lt;/span&gt;
  &lt;span style=&#34;color:#555&#34;&gt;{&lt;/span&gt;
    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; filteredDocuments&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;size&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;();&lt;/span&gt;
  &lt;span style=&#34;color:#555&#34;&gt;}&lt;/span&gt;

  &lt;span style=&#34;color:#99f&#34;&gt;@Override&lt;/span&gt;
  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;public&lt;/span&gt; Object &lt;span style=&#34;color:#c0f&#34;&gt;getValueAt&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;int&lt;/span&gt; rowIndex&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;int&lt;/span&gt; columnIndex&lt;span style=&#34;color:#555&#34;&gt;)&lt;/span&gt;
  &lt;span style=&#34;color:#555&#34;&gt;{&lt;/span&gt;
    &lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;int&lt;/span&gt; tableId &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;int&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;)&lt;/span&gt; filteredDocuments&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;get&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;rowIndex&lt;span style=&#34;color:#555&#34;&gt;).&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;get&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;tableId&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;);&lt;/span&gt;

    Document document &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; nitriteCollection&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;find&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;Filters&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;eq&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;tableId&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt; tableId&lt;span style=&#34;color:#555&#34;&gt;))&lt;/span&gt;
        &lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;firstOrDefault&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;();&lt;/span&gt;

    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;return&lt;/span&gt; document&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;get&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;attributeNames&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;get&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;columnIndex&lt;span style=&#34;color:#555&#34;&gt;));&lt;/span&gt;
  &lt;span style=&#34;color:#555&#34;&gt;}&lt;/span&gt;

  &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#078;font-weight:bold&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#c0f&#34;&gt;setFilteredIndices&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;Cursor find&lt;span style=&#34;color:#555&#34;&gt;)&lt;/span&gt;
  &lt;span style=&#34;color:#555&#34;&gt;{&lt;/span&gt;
    filteredDocuments&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;clear&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;();&lt;/span&gt;
    Document projection &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; Document&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;createDocument&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#c30&#34;&gt;&amp;#34;tableId&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;null&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;);&lt;/span&gt;
    RecordIterable&lt;span style=&#34;color:#555&#34;&gt;&amp;lt;&lt;/span&gt;Document&lt;span style=&#34;color:#555&#34;&gt;&amp;gt;&lt;/span&gt; documents &lt;span style=&#34;color:#555&#34;&gt;=&lt;/span&gt; find&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;project&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;projection&lt;span style=&#34;color:#555&#34;&gt;);&lt;/span&gt;

    &lt;span style=&#34;color:#069;font-weight:bold&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;Document document &lt;span style=&#34;color:#555&#34;&gt;:&lt;/span&gt; documents&lt;span style=&#34;color:#555&#34;&gt;)&lt;/span&gt;
    &lt;span style=&#34;color:#555&#34;&gt;{&lt;/span&gt;
      filteredDocuments&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;add&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;document&lt;span style=&#34;color:#555&#34;&gt;);&lt;/span&gt;
    &lt;span style=&#34;color:#555&#34;&gt;}&lt;/span&gt;
  &lt;span style=&#34;color:#555&#34;&gt;}&lt;/span&gt;
&lt;span style=&#34;color:#555&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This allows to easily filter collections and display the results.&lt;/p&gt;
&lt;p&gt;Sorting:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;nitriteTableModel&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;setFilteredIndices&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;nitriteCollection&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;find&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;Filters&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;eq&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;attributeNameField&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;getText&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(),&lt;/span&gt; attributeValueField&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;getText&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;())));&lt;/span&gt;
nitriteTableModel&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;fireTableDataChanged&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Filtering:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre style=&#34;background-color:#f0f3f3;-moz-tab-size:4;-o-tab-size:4;tab-size:4&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;nitriteTableModel&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;setFilteredIndices&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;nitriteCollection&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;find&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;FindOptions
                &lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;sort&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(&lt;/span&gt;attributeNameField&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;getText&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;(),&lt;/span&gt; SortOrder&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;Ascending&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;)));&lt;/span&gt;
nitriteTableModel&lt;span style=&#34;color:#555&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#309&#34;&gt;fireTableDataChanged&lt;/span&gt;&lt;span style=&#34;color:#555&#34;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;attributeNameField&lt;/code&gt; and &lt;code&gt;attributeValueField&lt;/code&gt; are basic &lt;code&gt;JTextField&lt;/code&gt; GUI elements that allow to specify which column should be used for filtering or sorting as well as which value the filtered elements have to match. Nitrite provides all the obvious filtering (&lt;code&gt;eq&lt;/code&gt;, &lt;code&gt;gt&lt;/code&gt;, &lt;code&gt;gte&lt;/code&gt;, &lt;code&gt;lt&lt;/code&gt;, &lt;code&gt;lte&lt;/code&gt;, &lt;code&gt;in&lt;/code&gt;, &amp;hellip;) and sorting (ascending, descending) options out of the box.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I have used Nitrite as &lt;code&gt;JTable&lt;/code&gt; backend with several million elements. The table performs amazingly smooth. Filtering and sorting on indexed attributes is almost instant as compared to several seconds or even minutes with the SQL-based approach described in the beginning.&lt;/p&gt;
&lt;p&gt;If you happen to know of a more extensive performance evaluation of Nitrite, ideally comparing it against embedded SQL-based systems, please post it in the comments or contact me directly. If there is none, I might do one myself in a future blog post.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>About Writing this Software Engineering Blog</title>
      <link>https://tkainrad.dev/posts/writing-a-swe-blog/</link>
      <pubDate>Sun, 05 May 2019 00:00:00 +0000</pubDate>
      
      <guid>https://tkainrad.dev/posts/writing-a-swe-blog/</guid>
      <description>&lt;h1 id=&#34;motivation&#34;&gt;Motivation&lt;/h1&gt;
&lt;p&gt;At the beginning of 2018, I finished my Master&amp;rsquo;s degree at &lt;em&gt;TU Wien&lt;/em&gt; and started to work full time as a Software Engineer. As I was already working part-time before, I knew what I was getting into. Still, I am continuously amazed about how dynamic our industry and the people working in it are. Especially now, with the all-around success of (F)OSS, the software technology landscape is changing incredibly fast. This blog should help both my readers and me to keep up with the latest trends. I also see it as a way to contribute to the international software community.&lt;/p&gt;
&lt;p&gt;Another reason for starting this blog is the inspiration from others. It seems like all the cool kids are doing it. A friend and former study colleague has already started &lt;a href=&#34;https://knasmueller.net/&#34;&gt;his blog&lt;/a&gt; a couple months ago. Other great blogs I read are from software engineering heavyweights &lt;a href=&#34;https://www.troyhunt.com/&#34;&gt;Troy Hunt&lt;/a&gt;, &lt;a href=&#34;https://www.hanselman.com/blog&#34;&gt;Scott Hanselman&lt;/a&gt;, and &lt;a href=&#34;https://blog.codinghorror.com/&#34;&gt;Jeff Atwood&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, I hope to further develop my writing skills. In general, I think writing in all forms is an underestimated skill in software engineering, and probably many other fields as well. However, since last year, when I finished my Master&amp;rsquo;s thesis and a subsequent &lt;a href=&#34;https://www.doi.org/10.1021/acs.jcim.8b00716&#34;&gt;journal article&lt;/a&gt;, my writing efforts have been more or less limited to communication and documentation tasks. This blog is a chance for me to change this.&lt;/p&gt;
&lt;h1 id=&#34;planned-topics&#34;&gt;Planned Topics&lt;/h1&gt;
&lt;p&gt;In general, I will keep this blog strictly on topics related to software engineering. Further, I will try to cover technologies that are (not yet) widely used and discussed elsewhere. My first two technical posts about &lt;a href=&#34;../aws-parallelcluster/&#34;&gt;AWS ParallelCluster&lt;/a&gt; and the &lt;a href=&#34;../nitrite-embedded-nosql-db/&#34;&gt;Nitrite embedded NoSQL database&lt;/a&gt; are good examples of this approach.&lt;/p&gt;
&lt;p&gt;At some point, I probably can&amp;rsquo;t restrain myself from writing about productivity tools, as trying out new ones is kind of my guilty pleasure. Thanks to a &lt;a href=&#34;https://twitter.com/mitsuhiko/status/1113805888278683650&#34;&gt;tweet from Austrian developer Armin Ronacher&lt;/a&gt; I am currently starting to use &lt;a href=&#34;https://www.notion.so/?r=cb45b9cefd824015a044b3336009be32&#34;&gt;Notion&lt;/a&gt;. It&amp;rsquo;s great, check it out!&lt;/p&gt;
&lt;h1 id=&#34;a-request-to-my-readers&#34;&gt;A Request to my Readers&lt;/h1&gt;
&lt;p&gt;Obviously, I am aware that the number of readers of this blog will be very limited. Nevertheless, I hope to receive some feedback. Please feel free to contact me via &lt;a href=&#34;mailto:thomas@tkainrad.dev&#34;&gt;email&lt;/a&gt; or &lt;a href=&#34;https://twitter.com/ThomasKainrad&#34;&gt;Twitter&lt;/a&gt;. You are welcome to point out any mistakes, recommend technologies, propose topics, or come forward with any other request you might have.&lt;/p&gt;
&lt;figure class=&#34;center-figure&#34;&gt;
    &lt;img src=&#34;https://imgs.xkcd.com/comics/blogging.png&#34;
         alt=&#34;My naive idea of blogging is to achieve reader engagement through worthwhile content. (Source: xkcd.com)&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;My naive idea of blogging is to achieve reader engagement through worthwhile content. (Source: xkcd.com)&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

</description>
    </item>
    
  </channel>
</rss>