<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.0">Jekyll</generator><link href="https://duseev.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://duseev.com/" rel="alternate" type="text/html" /><updated>2022-03-02T17:34:58+00:00</updated><id>https://duseev.com/feed.xml</id><title type="html">Vagiz Duseev</title><subtitle>Writing about software engineering.</subtitle><author><name>Vagiz Duseev</name></author><entry><title type="html">Getting started with Wutch</title><link href="https://duseev.com/articles/introducing-wutch/" rel="alternate" type="text/html" title="Getting started with Wutch" /><published>2021-04-04T20:00:00+00:00</published><updated>2021-04-04T20:00:00+00:00</updated><id>https://duseev.com/articles/introducing-wutch</id><content type="html" xml:base="https://duseev.com/articles/introducing-wutch/">&lt;p&gt;&lt;img src=&quot;https://github.com/vduseev/wutch/raw/master/docs/_static/wutch-demo.gif&quot; alt=&quot;Wutch Demo&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Wutch is a python based live server that observes changes in the given directories and
executes a shell command on each change. It also, optionally, renders the results in the
default web browser, automatically refreshing each web page after every change.&lt;/p&gt;

&lt;p&gt;You can use wutch with Sphinx, Jekyll, and other static site generators. On the GIF above
you can see how wutch builds its own documentation. It behaves just like a live server.
Adding any change causes a subprocess shell to run Sphinx (doc site generator used
by wutch) to rebuild its docs in real time.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h3 id=&quot;why-wutch&quot;&gt;Why wutch?&lt;/h3&gt;

&lt;p&gt;If you’ve ever worked with live servers popular among frontend developers you know how convenient
they are. Problem is you should either rely on the Live Server extension such as
&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer&quot;&gt;this one&lt;/a&gt; or
create a build pipeline using something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gulp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We wanted to create a command line tool that is able to rebuild documentation or website
on each change and automatically refresh browser’s webpage afterwards.&lt;/p&gt;

&lt;h3 id=&quot;features&quot;&gt;Features&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Watches multiple directories and glob file patters for changes&lt;/li&gt;
  &lt;li&gt;Able to ignore multiple directories and glob file patters&lt;/li&gt;
  &lt;li&gt;Runs given shell command on each change&lt;/li&gt;
  &lt;li&gt;Starts a live server (with an option to bind to a given port and address)&lt;/li&gt;
  &lt;li&gt;(optionally) Opens a browser when Wutch starts pointing to the build directory&lt;/li&gt;
  &lt;li&gt;Automatically refreshes web page on each build&lt;/li&gt;
  &lt;li&gt;Configurable timeout between rebuilds&lt;/li&gt;
  &lt;li&gt;Configurable binging port and host (can be used as a development web server)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;hot-to-install-wutch&quot;&gt;Hot to install wutch?&lt;/h3&gt;

&lt;p&gt;At the moment, wutch is distributed as a Python package for Python &amp;gt;= 3.8. In the future
a Homebrew formula, as well as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.deb&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.rpm&lt;/code&gt; distributions will be added.&lt;/p&gt;

&lt;p&gt;You can install wutch like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;wutch&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Or, to install for the current user only:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--user&lt;/span&gt; wutch&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;how-to-use-wutch&quot;&gt;How to use wutch?&lt;/h3&gt;

&lt;p&gt;After installation &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wutch&lt;/code&gt; binary becomes available. Now you can start a wutch watcher process.
By default, it will do the following&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;watch current directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./&lt;/code&gt; for changes&lt;/li&gt;
  &lt;li&gt;start a live server&lt;/li&gt;
  &lt;li&gt;open a web browser pointing to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./_build/index.html&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sphinx-build&lt;/code&gt; shell command on each change&lt;/li&gt;
  &lt;li&gt;auto-refresh web page on each build&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wutch&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;How to configure wutch to perform different actions? Wutch supports multiple sources of
configuration settings. They are loaded in a priority given below. Let’s take
a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;command&lt;/code&gt; parameter that specifies what shell command to run as an example here.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;command line options&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wutch &lt;span class=&quot;nt&quot;&gt;--command&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bundle exec jekyll build&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;environment variables starting with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WUTCH_&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;WUTCH_COMMAND&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;bundle exec jekyll build&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;configuration file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wutch.cfg&lt;/code&gt; present in the current directory&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;bundle exec jekyll build&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;default variables hardcoded into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wutch&lt;/code&gt; itself&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;sphinx-build&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Below is the full list of &lt;strong&gt;supported parameters&lt;/strong&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nt&quot;&gt;-h&lt;/span&gt;, &lt;span class=&quot;nt&quot;&gt;--help&lt;/span&gt;            show this &lt;span class=&quot;nb&quot;&gt;help &lt;/span&gt;message and &lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; COMMAND, &lt;span class=&quot;nt&quot;&gt;--command&lt;/span&gt; COMMAND
                      Shell &lt;span class=&quot;nb&quot;&gt;command &lt;/span&gt;executed &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;response to file changes. Defaults to: sphinx-build.
&lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;PATTERNS ...], &lt;span class=&quot;nt&quot;&gt;--patterns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;PATTERNS ...]
                      Matches paths with these patterns &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;separated by &lt;span class=&quot;s1&quot;&gt;' '&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; Defaults to: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'*'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-P&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;IGNORE_PATTERNS ...], &lt;span class=&quot;nt&quot;&gt;--ignore-patterns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;IGNORE_PATTERNS ...]
                      Ignores file changes &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;these patterns &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;separated by &lt;span class=&quot;s1&quot;&gt;' '&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; Defaults to: &lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;DIRS ...], &lt;span class=&quot;nt&quot;&gt;--dirs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;DIRS ...]
                      Directories to watch &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;separated by &lt;span class=&quot;s1&quot;&gt;' '&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; Defaults to: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'.'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-D&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;IGNORE_DIRS ...], &lt;span class=&quot;nt&quot;&gt;--ignore-dirs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;IGNORE_DIRS ...]
                      Ignore file changes &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;these directories &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;separated by &lt;span class=&quot;s1&quot;&gt;' '&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; Defaults to: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'_build'&lt;/span&gt;, &lt;span class=&quot;s1&quot;&gt;'build'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; WAIT, &lt;span class=&quot;nt&quot;&gt;--wait&lt;/span&gt; WAIT  Wait N seconds after the &lt;span class=&quot;nb&quot;&gt;command &lt;/span&gt;is finished before refreshing the web page. Defaults to: 3.
&lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt; BUILD, &lt;span class=&quot;nt&quot;&gt;--build&lt;/span&gt; BUILD
                      Build directory containing files to render &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;the browser. Defaults to: _build.
&lt;span class=&quot;nt&quot;&gt;-I&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;INJECT_PATTERNS ...], &lt;span class=&quot;nt&quot;&gt;--inject-patterns&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;INJECT_PATTERNS ...]
                      Patterns of files to inject with JS code that refreshes them on rebuild &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;separated by &lt;span class=&quot;s1&quot;&gt;' '&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt; Defaults to: &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'*.htm*'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; INDEX, &lt;span class=&quot;nt&quot;&gt;--index&lt;/span&gt; INDEX
                      File that will be opened &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;the browser with the start of the watcher. Defaults to: index.html.
&lt;span class=&quot;nt&quot;&gt;--host&lt;/span&gt; HOST           Host to &lt;span class=&quot;nb&quot;&gt;bind &lt;/span&gt;internal HTTP server to. Defaults to: localhost.
&lt;span class=&quot;nt&quot;&gt;--port&lt;/span&gt; PORT           TCP port to &lt;span class=&quot;nb&quot;&gt;bind &lt;/span&gt;internal HTTP server to. Defaults to: 5010.
&lt;span class=&quot;nt&quot;&gt;-B&lt;/span&gt; NO_BROWSER, &lt;span class=&quot;nt&quot;&gt;--no-browser&lt;/span&gt; NO_BROWSER
                      Do not open browser at wutch launch. Defaults to: False.
&lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; NO_SERVER, &lt;span class=&quot;nt&quot;&gt;--no-server&lt;/span&gt; NO_SERVER
                      Do not start the webserver, just launch the shell command. Defaults to: False.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;building-sphinx-documentation-with-wutch&quot;&gt;Building Sphinx documentation with Wutch&lt;/h4&gt;

&lt;p&gt;Wutch relies on itself when building and rendering its own documentation written with
Sphinx (see &lt;a href=&quot;https://github.com/vduseev/wutch/tree/master/docs&quot;&gt;wutch docs sources&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In order to set up a Sphinx doc development using wutch’s live server it’s enough
to place a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wutch.cfg&lt;/code&gt; file into the project folder and then simply run wutch binary.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;dirs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;docs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;patterns&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;*.rst&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;*.py&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;make -C docs build&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;docs/_build/html&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With these settings wutch will look for the changes happening in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.rst&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*.py&lt;/code&gt;
files in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./docs&lt;/code&gt; directory. For each change, wutch will run
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make -C docs build&lt;/code&gt; command from its &lt;a href=&quot;https://github.com/vduseev/wutch/blob/master/docs/Makefile&quot;&gt;Makefile&lt;/a&gt;.
It will then open the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./docs/_build/html/index.html&lt;/code&gt; file in the default browser
and will auto-refresh the browser page every rebuild.&lt;/p&gt;

&lt;h4 id=&quot;building-jekyll-with-wutch&quot;&gt;Building Jekyll with Wutch&lt;/h4&gt;

&lt;p&gt;Jekyll already comes with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll serve&lt;/code&gt; command that creates a server which
rebuilds the website for every change. However, jekyll is not able to refresh the
page in the browser automatically.&lt;/p&gt;

&lt;p&gt;Imagine you have a pretty standard Jekyll project structure. Below is a structure
of this blog.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;drwxr-xr-x  _drafts
drwxr-xr-x  _includes
drwxr-xr-x  _layouts
drwxr-xr-x  _posts
drwxr-xr-x  _site
drwxr-xr-x  assets
&lt;span class=&quot;nt&quot;&gt;-rwxr-xr-x&lt;/span&gt;  index.md
drwxr-xr-x  pages
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;  robots.txt
&lt;span class=&quot;nt&quot;&gt;-rwxr-xr-x&lt;/span&gt;  404.html
&lt;span class=&quot;nt&quot;&gt;-rwxr-xr-x&lt;/span&gt;  Gemfile
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;  Gemfile.lock
&lt;span class=&quot;nt&quot;&gt;-rwxr-xr-x&lt;/span&gt;  _config.yml&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here is how to build and run a live server for it with Wutch.
For this example we will be using command line options instead of the config file.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wutch &lt;span class=&quot;nt&quot;&gt;--ignore-dirs&lt;/span&gt; _site &lt;span class=&quot;nt&quot;&gt;--build&lt;/span&gt; _site &lt;span class=&quot;nt&quot;&gt;--command&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bundle exec jekyll build&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;using-wutch-as-live-server-for-frontend-builds&quot;&gt;Using Wutch as live server for frontend builds&lt;/h4&gt;

&lt;p&gt;Even though there are plenty of options in frontend to implement a live server that
watches for changes and rebuilds CSS and HTML, still, wutch is capable of that too.&lt;/p&gt;

&lt;p&gt;Imagine you have a Gulp based build pipeline for your website and you can run a build
using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gulp build&lt;/code&gt; command. Then all results of the build are dropped into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build&lt;/code&gt;
directory of the project.&lt;/p&gt;

&lt;p&gt;In that case we’d need to look for the changes in the current directory, but ignore
the changes in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build&lt;/code&gt; folder.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wutch &lt;span class=&quot;nt&quot;&gt;--ignore-dirs&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;build&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--build&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;build&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--command&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;gulp build&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;more-about-wutch&quot;&gt;More about Wutch&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Documentation: &lt;a href=&quot;https://wutch.readthedocs.io&quot;&gt;wutch.readthedocs.io&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Repo: &lt;a href=&quot;https://github.com/vduseev/wutch&quot;&gt;github.com/vduseev/wutch&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;PyPI package: &lt;a href=&quot;https://pypi.org/project/wutch/&quot;&gt;pypi.org/project/wutch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Vagiz Duseev</name></author><summary type="html">Wutch is a python based live server that observes changes in the given directories and executes a shell command on each change. It also, optionally, renders the results in the default web browser, automatically refreshing each web page after every change. You can use wutch with Sphinx, Jekyll, and other static site generators. On the GIF above you can see how wutch builds its own documentation. It behaves just like a live server. Adding any change causes a subprocess shell to run Sphinx (doc site generator used by wutch) to rebuild its docs in real time.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://i.ibb.co/x72ZZSv/with-page-pic.png" /><media:content medium="image" url="https://i.ibb.co/x72ZZSv/with-page-pic.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Designing Twitter</title><link href="https://duseev.com/articles/twitter-architecture/" rel="alternate" type="text/html" title="Designing Twitter" /><published>2018-06-05T20:00:00+00:00</published><updated>2018-06-05T20:00:00+00:00</updated><id>https://duseev.com/articles/twitter-architecture</id><content type="html" xml:base="https://duseev.com/articles/twitter-architecture/">&lt;p&gt;&lt;img src=&quot;https://image.ibb.co/doDhso/twitter_architecture.jpg&quot; alt=&quot;Designing Twitter Architecture&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This article describes several different approaches to designing a system like Twitter. The kind of task you could be asked to perform during a technical interview. Each presented architecture describes the system from a very high level, focusing on the main idea of the optimization or design rather than small details.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;table-of-contents&quot;&gt;Table of contents&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;#features&quot;&gt;Features&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#architectural-approaches&quot;&gt;Architectural approaches&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#relational-database&quot;&gt;Relational database&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#sharded-relational-database&quot;&gt;Sharded relational database&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#pre-calculated-feeds&quot;&gt;Pre-calculated feeds&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;features&quot;&gt;Features&lt;/h2&gt;

&lt;p&gt;First of all, let’s specify a list of features that will be present in the system.
Typically, a system like Twitter consists of user accounts that can follow each other and something called feed/dashboard, or timeline, a place where all posts made by user’s follow-pull are gathered and rendered in a reversed time manner.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Tweet&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Each user is able to tweet or post a simple textual message. For the sake of simplicity we don’t consider retweets, likes, or comments.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Follow&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Any user can follow any other user. When user follows someone else a subscription is created. Whenever followed user is posting something, the follower will see these tweets in his feed.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Feed&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Collection of tweets made by followed users organized in a time-reversed fashion. 
An important addition to mention is that a system like twitter prefers eventual consistence over immediate consistency. It is better to get fast read rather than immediate write.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;architectural-approaches&quot;&gt;Architectural approaches&lt;/h2&gt;

&lt;p&gt;We will review several approaches, starting with the simplest one, like storing everything in a relational database. But eventually the system is going to target millions of users making hundreds of thousands tweets a day.&lt;/p&gt;

&lt;p&gt;&lt;a name=&quot;relational-database&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;take-1-relational-database&quot;&gt;Take-1: Relational database&lt;/h3&gt;

&lt;h4 id=&quot;description&quot;&gt;Description&lt;/h4&gt;

&lt;p&gt;When taking the most direct approach, the obvious thing is to have a set of relational tables: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tweets&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;followers&lt;/code&gt;. 
&lt;em&gt;Users&lt;/em&gt; table is nothing but a storage of active/registered users. The &lt;em&gt;Followers&lt;/em&gt; table stores a row for each existing subscription of one user to another. This will be required when calculating the feed for each user.
Finally, &lt;em&gt;Tweets&lt;/em&gt; table contains all tweets with a foreign key specifying the author of each tweet.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://image.ibb.co/jmm4No/twitter_database_design.png&quot; alt=&quot;Twitter Database Design&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;upsides-&quot;&gt;Upsides 👍&lt;/h4&gt;

&lt;p&gt;The obvious strong side of this approach is its indisputable simplicity. Just one database to manage, several tables, everything is stored in one place. As less overhead as possible. To be fair, many start-ups rely on this exact approach when building their product at the very beginning. Tumblr is a good example of that.&lt;/p&gt;

&lt;h4 id=&quot;downsides-&quot;&gt;Downsides 👎&lt;/h4&gt;

&lt;p&gt;Well, while being the easiest to implement, this system is not very scalable. Of course, it can handle a hundred users. If everyone would subscribe to everyone the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;followers&lt;/code&gt; table would grow to be 10,000 rows, which is fine unless the user base starts to really grow.
You could imagine that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select&lt;/code&gt; statement to calculate the timeline of each user would eventually take forever to complete or would hit a memory boundary.&lt;/p&gt;

&lt;h4 id=&quot;technologies&quot;&gt;Technologies&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Database: &lt;strong&gt;MySQL/PostgreSQL&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Backend: &lt;strong&gt;Python (Django)/Ruby on Rails/PHP&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a name=&quot;sharded-relational-database&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;take-2-sharded-relational-database&quot;&gt;Take-2: Sharded relational database&lt;/h3&gt;

&lt;h4 id=&quot;description-1&quot;&gt;Description&lt;/h4&gt;

&lt;p&gt;A different approach could be evolved out of an original single database design. It is possible to significantly improve scalability of the database in this particular case by shadring actual tweet storage and indexes.
Since the feed is based on chronologically ordered collection of tweets, the sharding might be done on a time basis. 
Separating database indexes from the actual storage is an attempt to speed up query execution.
There would still be one master in each of the sections. However, by introducing slave replicas and shards we can significantly improve response times.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://image.ibb.co/npQaTT/twitter_sharded_design.png&quot; alt=&quot;Twitter Sharded Design&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;upsides--1&quot;&gt;Upsides 👍&lt;/h4&gt;

&lt;p&gt;Indexes are now stored in separate shards. The actual calculation of the timeline will be performed only by scanning index shards.&lt;/p&gt;

&lt;h4 id=&quot;downsides--1&quot;&gt;Downsides 👎&lt;/h4&gt;

&lt;p&gt;A design like this would eventually hit the read operation ceiling when talking to index shards.&lt;/p&gt;

&lt;h4 id=&quot;technologies-1&quot;&gt;Technologies&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Database: &lt;strong&gt;MySQL&lt;/strong&gt; &lt;em&gt;(no PostgreSQL due to the lack of decent logical replication)&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;Backend: &lt;strong&gt;Same&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a name=&quot;pre-calculated-feeds&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;take-3-pre-calculated-feeds&quot;&gt;Take-3: Pre-calculated feeds&lt;/h3&gt;

&lt;h4 id=&quot;description-2&quot;&gt;Description&lt;/h4&gt;

&lt;p&gt;If we bring the idea of trading off write speeds for read speed to its absolute, we should eventually take a look at the approach that pre-calculates the feeds (also called materialized timelines). The immediate consistency of the write operation is traded off for a faster feed read operation for each user.
In order to implement this, each time someone posts a new tweet the timeline of each user must be updated.
It seems obvious that some caching solution is in order here.
Memcached might be an option, but it stores the data in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blob&lt;/code&gt; format. Binary &lt;em&gt;blob&lt;/em&gt; format will require us to pull out the whole timeline of a particular user, append a new tweet to it, and then put it pack to the cache.&lt;/p&gt;

&lt;p&gt;Contrary, Redis has a notion of list data structure, which allows us to append tweets without extracting whole timeline from the cache.
With this approach, each time some user requests a feed, a pre-calculated ready-to-read feed will be obtained from the Redis.&lt;/p&gt;

&lt;p&gt;To make things even better, it would be nice to introduce a &lt;strong&gt;load balancer&lt;/strong&gt; which has been deliberately ignored in the previous considerations. A load balancer would choose a proper Redis instance to perform a read or write operation.&lt;/p&gt;

&lt;p&gt;The backend application would request a list of followers whenever some user posts a new tweet. Based on this list the backed will add new tweet to all of the required lists in Redis.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://image.ibb.co/gPnYF8/twitter_materialized_view.png&quot; alt=&quot;Twitter Materialized View&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;upsides--2&quot;&gt;Upsides 👍&lt;/h4&gt;

&lt;p&gt;The time complexity of read operation is &lt;em&gt;O(1)&lt;/em&gt;. Write complexity, however, is &lt;em&gt;O(n)&lt;/em&gt;. We must append a new tweet to every timeline that belongs to a follower.&lt;/p&gt;

&lt;h4 id=&quot;downsides--2&quot;&gt;Downsides 👎&lt;/h4&gt;

&lt;p&gt;The amount of stored data might become quite great in size. Redis will require a replication in order to keep serving requests even when one instance with pre-calculated feed dies.
Since Redis is an in-memory database, each instance would also need a great amount of RAM to operate properly, which is an obvious price we pay for an instant access to user’s timeline.&lt;/p&gt;

&lt;p&gt;Another dangerous case is presented by the users with millions of followers. Any update they post to their followers must be written to millions of Redis lists for pre-calculated timelines.
When taking replicas into account, this could take an unpredictable amount of time.&lt;/p&gt;

&lt;p&gt;A possible solution to that is to keep the tweets of popular users somewhere separate and avoid the whole on-write timeline update story completely. 
Instead, whenever any of their followers requests a tweet feed, the backend app will manually merge the tweets of the popular user inside the timeline. 
Doing this in runtime during read operation is obviously not a &lt;em&gt;O(1)&lt;/em&gt; complexity. However, this prevents us from generating a write avalanche each time they post something.&lt;/p&gt;

&lt;h4 id=&quot;technologies-2&quot;&gt;Technologies&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Database: &lt;strong&gt;Redis (cached feed), MySQL (followers table)&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Backend: &lt;strong&gt;Same&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;</content><author><name>Vagiz Duseev</name></author><summary type="html">This article describes several different approaches to designing a system like Twitter. The kind of task you could be asked to perform during a technical interview. Each presented architecture describes the system from a very high level, focusing on the main idea of the optimization or design rather than small details.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://image.ibb.co/doDhso/twitter_architecture.jpg" /><media:content medium="image" url="https://image.ibb.co/doDhso/twitter_architecture.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Make Vim detect Pipenv based Python environment</title><link href="https://duseev.com/articles/vim-python-pipenv/" rel="alternate" type="text/html" title="Make Vim detect Pipenv based Python environment" /><published>2018-04-12T06:00:00+00:00</published><updated>2018-04-12T06:00:00+00:00</updated><id>https://duseev.com/articles/vim-python-pipenv</id><content type="html" xml:base="https://duseev.com/articles/vim-python-pipenv/">&lt;p&gt;&lt;strong&gt;Vim&lt;/strong&gt; can be a great IDE. If I could you use just one word to describe it, that word would be “fast”. Vim can be easily configured to be a powerful IDE for Python development. However, as times change, as does the official recommended packaging tool for Python – it was &lt;strong&gt;Pip&lt;/strong&gt; before, now it’s &lt;strong&gt;Pipenv&lt;/strong&gt;, a high level wrapper around &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pip&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;virtualenv&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://image.ibb.co/mu5ENo/vim_python_pipenv.jpg&quot; alt=&quot;Vim detects Pipenv python environment config&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Setting up a proper integration between Vim and Pipenv may seem like a cumbersome task. It entirely depends on what you want to achieve. This article focuses on integration between &lt;strong&gt;Vim&lt;/strong&gt;, &lt;strong&gt;Pipenv&lt;/strong&gt;, and &lt;strong&gt;YouCompleteMe&lt;/strong&gt; – a fast, fuzzy-search code completion engine for Vim. But some of the options described in the article are suitable for other setups as well.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;vim-as-a-python-ide&quot;&gt;Vim as a Python IDE&lt;/h2&gt;

&lt;p&gt;I could talk about Vim all day, if I had a chance. Vim requires less resources to run than most of the modern IDEs. It’s faster in many ways, even when it comes to syntax verification, file reading, and handling of large repositories. Vim has a tremendously large amount of commands, plugins, and shortcuts which are all configurable and allow you to navigate and edit in a blink of an eye. And last but not least, you can usually find an instance of Vim (or at least &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vi&lt;/code&gt;) on any Unix based OS, which proves to be especially useful for DevOps engineers. Same powerful editor on a server and on your workstation.&lt;/p&gt;

&lt;p&gt;Here is a small list of plugins you could install to turn Vim into a Python IDE:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-vim&quot; data-lang=&quot;vim&quot;&gt;&lt;span class=&quot;c&quot;&gt;&quot; Smart auto-indentation for Python&lt;/span&gt;
Plugin &lt;span class=&quot;s1&quot;&gt;'vim-scripts/indentpython.vim'&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; Auto-completing engine&lt;/span&gt;
Plugin &lt;span class=&quot;s1&quot;&gt;'Valloric/YouCompleteMe'&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; Syntax checker&lt;/span&gt;
Plugin &lt;span class=&quot;s1&quot;&gt;'vim-syntastic/syntastic'&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; Python backend for 'syntastic'&lt;/span&gt;
Plugin &lt;span class=&quot;s1&quot;&gt;'nvie/vim-flake8'&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; Status bar (powerline)&lt;/span&gt;
Plugin &lt;span class=&quot;s1&quot;&gt;'vim-airline/vim-airline'&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; Awesome staring screen for Vim&lt;/span&gt;
Plugin &lt;span class=&quot;s1&quot;&gt;'mhinz/vim-startify'&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; File manager&lt;/span&gt;
Plugin &lt;span class=&quot;s1&quot;&gt;'scrooloose/nerdtree'&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; Search bar&lt;/span&gt;
Plugin &lt;span class=&quot;s1&quot;&gt;'kien/ctrlp.vim'&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; Theme&lt;/span&gt;
Plugin &lt;span class=&quot;s1&quot;&gt;'crusoexia/vim-monokai'&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; Powerful commenting utility&lt;/span&gt;
Plugin &lt;span class=&quot;s1&quot;&gt;'scrooloose/nerdcommenter'&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; Rich python syntax highlighting&lt;/span&gt;
Plugin &lt;span class=&quot;s1&quot;&gt;'kh3phr3n/python-syntax'&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Obviously, there is so much more to Vim than just a list of plugins. Vim’s configuration file – &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt; – can become quite large, just because there are so many things you can configure in Vim. However, complete configuration of Vim for Python development is a topic of a different article.&lt;/p&gt;

&lt;h2 id=&quot;virtualenv-support&quot;&gt;VirtualEnv Support&lt;/h2&gt;

&lt;p&gt;One of the issues you can discover is that by default Vim has no notion of a virtual environment in which you are supposed to run. In order to perform any auto-completion or syntax checking tasks Vim utilizes the default python interpreter found in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt; environment variable.&lt;/p&gt;

&lt;p&gt;The classic way to make Vim aware of project’s virtual environment is to check the list of environment variables to detect whether we are running inside a virtual environment and activate it. This approach is documented on the &lt;a href=&quot;https://realpython.com/vim-and-python-a-match-made-in-heaven/#virtualenv-support&quot;&gt;realpython.com&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-vim&quot; data-lang=&quot;vim&quot;&gt;&lt;span class=&quot;c&quot;&gt;&quot;python with virtualenv support&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;py&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; EOF
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; os
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; sys
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'VIRTUAL_ENV'&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; os&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;environ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    project_base_dir &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;environ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'VIRTUAL_ENV'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    activate_this &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; os&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;project_base_dir&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'bin/activate_this.py'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    execfile&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;activate_this&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;__file__&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;activate_this&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
EOF&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Unfortunately, this no longer works if we deal with Pipenv based virtual environments. The environment itself might not even be in the same directory, or not even under default path &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.local/share/virtualenvs/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, we can take advantage of Pipenv’s own commands to determine if we are supposed to be running in a virtual environment.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;pipenv &lt;span class=&quot;nt&quot;&gt;--venv&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When we have a PyEnv installed it will detect our call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipenv&lt;/code&gt;. Let’s take a look at possible situations:&lt;/p&gt;

&lt;h3 id=&quot;directory-is-not-configured-for-pyenv-or-pipenv&quot;&gt;Directory is not configured for PyEnv or Pipenv&lt;/h3&gt;

&lt;p&gt;In this case both python interpreter and Pipenv are not configured for the current directory. PyEnv reports that it could, theoretically, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipenv&lt;/code&gt;, which is available in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3.6.5&lt;/code&gt; installation, but would need you to indicate that explicitly.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Remove PyEnv configuration file for experiment&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; .python-version
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pipenv &lt;span class=&quot;nt&quot;&gt;--venv&lt;/span&gt;
pyenv: pipenv: &lt;span class=&quot;nb&quot;&gt;command &lt;/span&gt;not found

The &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;pipenv&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;command &lt;/span&gt;exists &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;these Python versions:
3.6.5&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The same behavior will be observed if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipenv&lt;/code&gt; is not installed as a package in the interpreter of choice. Let’s say, we specify a python version &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3.4.3&lt;/code&gt; in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.python-verison&lt;/code&gt; file, which has no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipenv&lt;/code&gt; installed. Same error will be thrown out by PyEnv in that case.&lt;/p&gt;

&lt;h3 id=&quot;directory-is-configured-for-pyenv-but-no-virtual-environment-is-created-yet&quot;&gt;Directory is configured for PyEnv, but no virtual environment is created yet&lt;/h3&gt;

&lt;p&gt;Here, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.python-version&lt;/code&gt; file exists for PyEnv to detect. And the python interpreter that we specified in the file has Pipenv installed as a package. In that case Pipenv will indicate that no virtual environment has been created yet.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Create PyEnv configuration file&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;3.6.5&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; .python-version
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pipenv &lt;span class=&quot;nt&quot;&gt;--venv&lt;/span&gt;
No virtualenv has been created &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;this project yet!&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;directory-is-configured-for-pyenv-and-virtual-environment-exists&quot;&gt;Directory is configured for PyEnv and virtual environment exists&lt;/h3&gt;

&lt;p&gt;In this case &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyenv --venv&lt;/code&gt; returns full path to the virtual environment utilized in this project.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;pipenv &lt;span class=&quot;nt&quot;&gt;--venv&lt;/span&gt;
/Users/user/.local/share/virtualenvs/.aws-MRQhgJfv&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;making-it-work-in-vimrc&quot;&gt;Making it work in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;We can take advantage of the fact that whenever we call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipenv --venv&lt;/code&gt; its return code will indicate successful (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sys.exit(0)&lt;/code&gt;) or unsuccessful (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sys.exit(1)&lt;/code&gt;) run.&lt;/p&gt;

&lt;p&gt;There is a variable named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shell_error&lt;/code&gt; in Vim, and, as you probably figured out, it contains the last exit code of the executed shell command.&lt;/p&gt;

&lt;p&gt;If we make a call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipenv --venv&lt;/code&gt; from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt;, we will either get a correct path, or one of the error messages we observed earlier. In order to make a call we can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;system()&lt;/code&gt; function of Vim:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-vim&quot; data-lang=&quot;vim&quot;&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; pipenv_venv_path &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'pipenv --venv'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then, simply by checking the value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shell_error&lt;/code&gt; variable, we can find out if the call was successful.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-vim&quot; data-lang=&quot;vim&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; shell_error &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;&quot; do one thing&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;&quot; do another thing&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;endif&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Since in this article we set up Python’s auto-completion for YouCompleteMe engine, we will use YCM’s global variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;g:ycm_python_binary_path&lt;/code&gt; to point it to the correct executable of proper virtual environment.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-vim&quot; data-lang=&quot;vim&quot;&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; venv_path &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;substitute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;pipenv_venv_path&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'\n'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;g:ycm_python_binary_path&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; venv_path &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'/bin/python'&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The final script that we’ll add to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vimrc&lt;/code&gt; will lok like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-vim&quot; data-lang=&quot;vim&quot;&gt;&lt;span class=&quot;c&quot;&gt;&quot; Point YCM to the Pipenv created virtualenv, if possible&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; At first, get the output of 'pipenv --venv' command.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; pipenv_venv_path &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'pipenv --venv'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; The above system() call produces a non zero exit code whenever&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; a proper virtual environment has not been found.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; So, second, we only point YCM to the virtual environment when&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; the call to 'pipenv --venv' was successful.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; Remember, that 'pipenv --venv' only points to the root directory&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; of the virtual environment, so we have to append a full path to&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;&quot; the python executable.&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; shell_error &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; venv_path &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;substitute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;pipenv_venv_path&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'\n'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;g:ycm_python_binary_path&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; venv_path &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'/bin/python'&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;g:ycm_python_binary_path&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'python'&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;endif&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This script can be further modified to support different completion engines or even other types of plugins, but the essence remains the same – determine whether Vim is running in the correctly configured Pipenv directory.&lt;/p&gt;</content><author><name>Vagiz Duseev</name></author><summary type="html">Vim can be a great IDE. If I could you use just one word to describe it, that word would be “fast”. Vim can be easily configured to be a powerful IDE for Python development. However, as times change, as does the official recommended packaging tool for Python – it was Pip before, now it’s Pipenv, a high level wrapper around pip and virtualenv. Setting up a proper integration between Vim and Pipenv may seem like a cumbersome task. It entirely depends on what you want to achieve. This article focuses on integration between Vim, Pipenv, and YouCompleteMe – a fast, fuzzy-search code completion engine for Vim. But some of the options described in the article are suitable for other setups as well.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://image.ibb.co/mu5ENo/vim_python_pipenv.jpg" /><media:content medium="image" url="https://image.ibb.co/mu5ENo/vim_python_pipenv.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Perfect AWS CLI setup</title><link href="https://duseev.com/articles/perfect-aws-cli-setup/" rel="alternate" type="text/html" title="Perfect AWS CLI setup" /><published>2018-04-07T08:00:00+00:00</published><updated>2018-04-07T08:00:00+00:00</updated><id>https://duseev.com/articles/perfect-aws-cli-setup</id><content type="html" xml:base="https://duseev.com/articles/perfect-aws-cli-setup/">&lt;p&gt;I deploy to AWS a lot. I believe it’s fine to use AWS GUI when you explore things, but otherwise it is better to write scripts to achieve results. Be it Bash scripts that use AWS CLI or Python scripts that use boto3 library. Writing scripts guarantees that when you forget how to properly deploy a cluster of ElasticSearch instances and shards you will just use your script instead of researching AWS documentation again. AWS CLI is a Python library installed via pip.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://image.ibb.co/eKxuNo/aws_cli_thumb.jpg&quot; alt=&quot;AWS CLI isolated clean installation on Unix&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I try to keep the installation of AWS CLI isolated from everything else. Making it possible to have multiple installations with different versions. Here is how I achieve that on MacOS.&lt;/p&gt;

&lt;!--more--&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.aws/&lt;/code&gt; directory. This directory will be automatically created when you configure your AWS CLI with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aws configure&lt;/code&gt; command. However, I prefer to create it ahead of time, because the installation will be kept here.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.aws/.python-version&lt;/code&gt; file that contains Python version that will be used for AWS CLI. As of now, this file contains &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3.6.5&lt;/code&gt;. This, of course, assumes that you use Pyenv to manage Python installations. Also the version that you chose must have Pipenv installed in it.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awscli&lt;/code&gt; library. Once you are in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.aws/&lt;/code&gt; directory just run&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pipenv install awscli
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;This will create a dedicated virtual environment and install AWS CLI there so that it’s exclusively available only from that place. Pipenv will know which virtual environment to use in this directory thanks to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pipfile.lock&lt;/code&gt; file.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;We have an isolated and clean installation of AWS CLI. Now, how can we make it available across the system? At the moment the only way to start AWS CLI is to fire up Pipenv from the directory with Pipfile.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pipenv run aws
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;Instead, let’s make a symlink that will redirect any requests to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aws&lt;/code&gt; command into our dedicated virtual environment. It’s pretty easy.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;Create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.aws/bin/&lt;/code&gt; directory.&lt;/li&gt;
      &lt;li&gt;Create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.aws/bin/aws&lt;/code&gt; file there with the following script inside:&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;    &lt;span class=&quot;c&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# The line above ensures cross compatibility in MacOS&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# Set ENV variable for PyEnv to know which interpreter to use&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# This will not work if you no longer have 3.6.5 version in Pyenv!&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PYENV_VERSION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;3.6.5

    &lt;span class=&quot;c&quot;&gt;# Set PIPENV location ENV variable to tell Pipenv where to look for virtual environment.&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PIPENV_PIPFILE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;~/.aws/Pipfile

    &lt;span class=&quot;c&quot;&gt;# When this command is executed following happens:&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# 1. Pyenv starts and uses shims to look for the pipenv module in the&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#    3.6.5 installation of Python. Then it starts pipenv&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# 2. Pipenv reads Pipfile location from environment variable of the&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#    shell that we just set and finds the aws executable&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#    in the dedicated virtual evnironment.&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;# 3. We pass all the arguments in our script to aws executable using&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;#    &quot;$@&quot; bash directive.&lt;/span&gt;
    pipenv run aws &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Make the script executable&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;chmod a+x ~/.aws/bin/aws
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Create a symlink to this executable script&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;    &lt;span class=&quot;nb&quot;&gt;sudo ln&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; ~/.aws/bin/aws /usr/local/bin
    &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Voilà, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aws&lt;/code&gt; executable is now available from any directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;  &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;
  /Users/user/

  &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;aws &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
  aws-cli/1.15.0 Python/3.6.5 Darwin/17.4.0 botocore/1.10.0
  &lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content><author><name>Vagiz Duseev</name></author><summary type="html">I deploy to AWS a lot. I believe it’s fine to use AWS GUI when you explore things, but otherwise it is better to write scripts to achieve results. Be it Bash scripts that use AWS CLI or Python scripts that use boto3 library. Writing scripts guarantees that when you forget how to properly deploy a cluster of ElasticSearch instances and shards you will just use your script instead of researching AWS documentation again. AWS CLI is a Python library installed via pip. I try to keep the installation of AWS CLI isolated from everything else. Making it possible to have multiple installations with different versions. Here is how I achieve that on MacOS.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://image.ibb.co/eKxuNo/aws_cli_thumb.jpg" /><media:content medium="image" url="https://image.ibb.co/eKxuNo/aws_cli_thumb.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Choosing between Ruby environment managers: rbenv vs. RVM</title><link href="https://duseev.com/articles/rbenv-vs-rvm/" rel="alternate" type="text/html" title="Choosing between Ruby environment managers: rbenv vs. RVM" /><published>2018-02-05T00:29:01+00:00</published><updated>2018-02-05T00:29:01+00:00</updated><id>https://duseev.com/articles/rbenv-vs-rvm</id><content type="html" xml:base="https://duseev.com/articles/rbenv-vs-rvm/">&lt;p&gt;Using a system’s default Ruby interpreter to develop projects and install required &lt;a href=&quot;http://guides.rubygems.org/what-is-a-gem/&quot;&gt;gemsets&lt;/a&gt; is a sure path to dependency nightmare. Each project needs its own environment independent from others. At the same time, the system’s installation of Ruby should be left intact.&lt;/p&gt;

&lt;p&gt;This article gives a quick overview of the popular Ruby environment managers and provides a concise installation instruction for a painless setup on MacOS X.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://image.ibb.co/hebX8T/rbenv_vs_rvm.jpg&quot; alt=&quot;rbenv vs. RVM&quot; /&gt;&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;&lt;em&gt;To quickly jump to the environment manager installation click &lt;a href=&quot;#macos-x-installation&quot;&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;p&gt;There are two main options for environment management when it comes to Ruby. First one is the &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt;. RVM was the first decent environment manager which could manage multiple interpreters and per-project based &lt;a href=&quot;http://guides.rubygems.org/what-is-a-gem/&quot;&gt;gemsets&lt;/a&gt;, it is still very popular among Ruby developers. Although, not only the installation process of it is somewhat unusual for these days, but is also messes up with a lot of things in the system in a magic and incomprehensible way. For instance, &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt; ensures that a correct version of Ruby will be utilized in a project by replacing original &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cd&lt;/code&gt; command in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*nix&lt;/code&gt; systems, which is way too much for nothing else but Ruby environment manager.&lt;/p&gt;

&lt;p&gt;The other known option is called &lt;a href=&quot;https://github.com/rbenv/rbenv&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv&lt;/code&gt;&lt;/a&gt;. It’s a much more simpler and reliable solution, and it does not mess the system up, keeping everything local and manageable.&lt;/p&gt;

&lt;h3 id=&quot;rvm-and-its-downsides&quot;&gt;RVM and its downsides&lt;/h3&gt;

&lt;p&gt;So, what’s wrong with the &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt;? The installation of &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt; is quite bizarre for anyone without a background in Linux systems administration, and project’s homepage leaves even more for a reader to guess regarding RVM’s internal logic and implementation. &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt; gives an impression of the tool that mingles with the system in an irreversible way. Obviously, it’s a tested, trusted, and community verified tool that does not perform any malicious actions with your system, but the first impression it gives doesn’t look like something you would expect from a user friendly environment manager.&lt;/p&gt;

&lt;p&gt;The installation requires you to add a pair of public GPG keys of project’s maintainers to your GPG keychain and then download and run a 1000 lines long bash script that will do the installation for you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;First, adding GPG keys:&lt;/em&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Add public GPG key of the RVM's authors to your GPG keychain&lt;/span&gt;
gpg &lt;span class=&quot;nt&quot;&gt;--keyserver&lt;/span&gt; hkp://keys.gnupg.net &lt;span class=&quot;nt&quot;&gt;--recv-keys&lt;/span&gt; 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB

&lt;span class=&quot;c&quot;&gt;# Result:&lt;/span&gt;
gpg: key 105BD0E739499BDB: public key &lt;span class=&quot;s2&quot;&gt;&quot;Piotr Kuczynski &amp;lt;piotr.kuczynski@gmail.com&amp;gt;&quot;&lt;/span&gt; imported
gpg: key 3804BB82D39DC0E3: 82 signatures not checked due to missing keys
gpg: key 3804BB82D39DC0E3: public key &lt;span class=&quot;s2&quot;&gt;&quot;Michal Papis (RVM signing) &amp;lt;mpapis@gmail.com&amp;gt;&quot;&lt;/span&gt; imported
gpg: no ultimately trusted keys found
gpg: Total number processed: 2
gpg:               imported: 2&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next thing that RMV’s website will suggest is to run the following command to continue installation:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;se&quot;&gt;\c&lt;/span&gt;url &lt;span class=&quot;nt&quot;&gt;-sSL&lt;/span&gt; https://get.rvm.io | bash &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; stable&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This line downloads and runs RVM’s bash installation script.&lt;/p&gt;

&lt;p&gt;Before running any unknown script I usually stop and suspend the installation process. You never know what that script is going to do to your system unless your read through it. RVM skips the “read” part right away. I’d call such installation process a bad practice. How difficult will it be to remove RVM from the system and clean up the traces? Was it to hard for creators to enable a Homebrew based installation? You can’t tell until you spend a considerable amount of time searching for the answers. And, again, doing a deep research of what’s supposed to be a simple Ruby environment manager is not something you would expect from a user friendly tool.&lt;/p&gt;

&lt;p&gt;Interestingly enough, if prior to going all in and running an unknown script you think ahead and decide to check the official website for the &lt;strong&gt;uninstalling instructions, you won’t find them&lt;/strong&gt;. There is a single mention of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;implode&lt;/code&gt; command on the &lt;a href=&quot;https://rvm.io/rvm/cli&quot;&gt;RVM’s CLI usage&lt;/a&gt; page. But according to that page:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;implode   - removes all ruby installations it manages, everything in ~/.rvm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;implode&lt;/code&gt; does not uninstall the RVM itself. Basically, there is no way to automatically uninstall &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt; other than manually cleaning up everything that the installation did to your system. And that’s a huge downside.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The only way to uninstall &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt; is to manually clean up everything it did to your system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you proceed with the installation and run the second step from RVM’s homepage, the bash script will be downloaded and executed.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Download RVM's installation script and install it&lt;/span&gt;
&lt;span class=&quot;se&quot;&gt;\c&lt;/span&gt;url &lt;span class=&quot;nt&quot;&gt;-sSL&lt;/span&gt; https://get.rvm.io | bash &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; stable

&lt;span class=&quot;c&quot;&gt;# Result:&lt;/span&gt;
Downloading https://github.com/rvm/rvm/archive/1.29.3.tar.gz
Downloading https://github.com/rvm/rvm/releases/download/1.29.3/1.29.3.tar.gz.asc
gpg: Signature made Sun Sep 10 22:59:21 2017 CEST
&lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
Installing RVM to /Users/user/.rvm/
    RVM PATH line found &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; /Users/user/.mkshrc.
    RVM PATH line not found &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;Bash or Zsh, rerun this &lt;span class=&quot;nb&quot;&gt;command &lt;/span&gt;with &lt;span class=&quot;s1&quot;&gt;'--auto-dotfiles'&lt;/span&gt; flag to fix it.
    Adding rvm loading line to /Users/user/.profile /Users/user/.bash_profile /Users/user/.zlogin.
Installation of RVM &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; /Users/user/.rvm/ is almost &lt;span class=&quot;nb&quot;&gt;complete&lt;/span&gt;:
&lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
Searching &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;binary rubies, this might take some time.
No binary rubies available &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt;: osx/10.13/x86_64/ruby-2.4.1.
&lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
Installing Ruby from &lt;span class=&quot;nb&quot;&gt;source &lt;/span&gt;to: /Users/user/.rvm/rubies/ruby-2.4.1, this may take a &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;depending on your cpu&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;...
&lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; To start using RVM you need to run &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; /Users/user/.rvm/scripts/rvm&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;all your open shell windows, &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;rare cases you need to reopen all shell windows.&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you are as impatient as me, you would most likely hope that at least the usage of RVM is clear and simple after all that unknown compilation-installation-mess. I would argue that it’s not. Let’s think about a first time Ruby user who managed to install &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt;. After spending around 10 minutes reading, trying to understand, and installing &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt; the user will search the homepage for the link that describes some usage examples or the basics:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://image.ibb.co/hwr9Cn/rvm_doc_links.jpg&quot; alt=&quot;RVM homepage documentation links&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Did you find a page that explains the &lt;a href=&quot;https://rvm.io/rvm/basics&quot;&gt;usage&lt;/a&gt;? Or maybe, &lt;a href=&quot;https://rvm.io/rvm/cli&quot;&gt;CLI usage&lt;/a&gt; would be a better page to look at for the  first time user? You would certainly hope that at least the basics are short and concise, because an environment manager is not the final goal of the whole process. The final goal is to develop using Ruby in a clean and isolated fashion.&lt;/p&gt;

&lt;p&gt;The usage instruction of RVM is muddy and unclear. The understanding of correct way to use the tool might require even more time than its installation. So, if you have installed RVM by mistake, here is the way to clean everything up and uninstall &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt; completely.&lt;/p&gt;

&lt;h3 id=&quot;cleaning-up-after-rvm&quot;&gt;Cleaning up after RVM&lt;/h3&gt;

&lt;p&gt;RVM can be uninstalled in a several steps. Here are two topics on StackOverflow covering the uninstallation of &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt;: &lt;a href=&quot;https://stackoverflow.com/questions/3558656/how-can-i-remove-rvm-ruby-version-manager-from-my-system&quot;&gt;How can I remove RVM (Ruby Version Manager) from my system?&lt;/a&gt; and &lt;a href=&quot;https://stackoverflow.com/questions/3950260/howto-uninstall-rvm&quot;&gt;Howto Uninstall RVM [duplicate]&lt;/a&gt;.&lt;/p&gt;

&lt;h4 id=&quot;0-delete-rvm-installed-rubies&quot;&gt;0. Delete RVM installed rubies&lt;/h4&gt;

&lt;p&gt;If you have already installed and integrated &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt; into your shell, you will need to start with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rvm implode&lt;/code&gt; command.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Check that RVM exists in your path&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$PATH&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;rvm

&lt;span class=&quot;c&quot;&gt;# If grep does not return anything, then RVM is not integrated into your shell&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# yet. Otherwise, run &quot;rvm implode&quot; to uninstall ruby interpreters&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# installed by RVM&lt;/span&gt;
rvm implode&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;1-removing-the-rvm-directory&quot;&gt;1. Removing the ~/.rvm directory&lt;/h4&gt;

&lt;p&gt;Some size stats before removing the directory&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Check disk usage of the ~/.rvm directory&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# -s displays an entry for each specified file or directory&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# -h prints the result in a &quot;Human-readable&quot; format.&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;#    e.g., Byte, Kilobyte, Megabyte, Gigabyte, Terabyte and Petabyte.&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;du&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-sh&lt;/span&gt; ~/.rvm
&lt;span class=&quot;c&quot;&gt;# Result:&lt;/span&gt;
181M	/Users/user/.rvm&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The pure default installation of &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt; on MacOS takes 181 Mb.&lt;/p&gt;

&lt;p&gt;Remove RVM’s directory&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rf&lt;/span&gt; ~/.rvm&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;2-cleaning-shell-configuration-files&quot;&gt;2. Cleaning shell configuration files&lt;/h4&gt;
&lt;p&gt;Check the following files for the references to RVM and remove the RVM line from each of them:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~/.bashrc
~/.bash_profile
~/.profile
~/.zshrc
~/.zlogin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;References look like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/.rvm/scripts/rvm&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/.rvm/scripts/rvm&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Load RVM into a shell session *as a function*&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We can do a quick &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep&lt;/code&gt; to check if &lt;a href=&quot;https://rvm.io/&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RVM&lt;/code&gt;&lt;/a&gt; put itself there.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; ~/.bashrc | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;rvm
&lt;span class=&quot;c&quot;&gt;# Result:&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/.rvm/scripts/rvm&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/.rvm/scripts/rvm&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Load RVM into a shell session *as a function*&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;3-removing-unnecessary-gpg-keys&quot;&gt;3. Removing unnecessary GPG keys&lt;/h4&gt;

&lt;p&gt;To remove public GPG keys of RVM maintainers we will need their user names. We can find them in the full list of public keys stored in GPG.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;gpg &lt;span class=&quot;nt&quot;&gt;--list-keys&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Result:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
pub   rsa4096 2016-11-11 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;SC]
      7D2BAF1CF37B13E2069D6956105BD0E739499BDB
uid           &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; unknown] Piotr Kuczynski &amp;lt;piotr.kuczynski@gmail.com&amp;gt;
sub   rsa4096 2016-11-11 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;E]

pub   rsa4096 2014-10-28 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;SC]
      409B6B1796C275462A1703113804BB82D39DC0E3
uid           &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; unknown] Michal Papis &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;RVM signing&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &amp;lt;mpapis@gmail.com&amp;gt;
uid           &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; unknown] Michal Papis &amp;lt;michal.papis@toptal.com&amp;gt;
uid           &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; unknown] &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;jpeg image of size 5015]
sub   rsa2048 2015-11-02 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;E]
sub   rsa4096 2014-10-28 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;S] &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;expires: 2019-03-09]
&lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can use any representation of user name in any &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uid&lt;/code&gt; row listed in a key to delete it. I will use email address.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Delete key&lt;/span&gt;
gpg &lt;span class=&quot;nt&quot;&gt;--delete-key&lt;/span&gt; piotr.kuczynski@gmail.com
&lt;span class=&quot;c&quot;&gt;# Result:&lt;/span&gt;
gpg &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;GnuPG&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 2.2.3&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; Copyright &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;C&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 2017 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pub  rsa4096/105BD0E739499BDB 2016-11-11 Piotr Kuczynski &amp;lt;piotr.kuczynski@gmail.com&amp;gt;

Delete this key from the keyring? &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;y/N&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; y
&lt;span class=&quot;c&quot;&gt;# Piotr's key is removed now&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Delete Michal's key&lt;/span&gt;
gpg &lt;span class=&quot;nt&quot;&gt;--delete-key&lt;/span&gt; mpapis@gmail.com
gpg &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;GnuPG&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 2.2.3&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; Copyright &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;C&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 2017 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pub  rsa4096/3804BB82D39DC0E3 2014-10-28 Michal Papis &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;RVM signing&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &amp;lt;mpapis@gmail.com&amp;gt;

Delete this key from the keyring? &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;y/N&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; y
&lt;span class=&quot;c&quot;&gt;# Both public keys are now removed&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;rbenv&quot;&gt;rbenv&lt;/h3&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/rbenv/rbenv&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv&lt;/code&gt;&lt;/a&gt; is short for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ruby Environment&lt;/code&gt;. It’s a different command line tool that enables you to quickly and easily switch between different rubies installed on your system.&lt;/p&gt;

&lt;p&gt;What I like about &lt;a href=&quot;https://github.com/rbenv/rbenv&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv&lt;/code&gt;&lt;/a&gt; is it’s simplicity and obviousness. It might look especially familiar for those coming from Python background, because rbenv, in fact, looks a lot like &lt;a href=&quot;https://github.com/pyenv/pyenv&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pyenv&lt;/code&gt;&lt;/a&gt; — a python version manager forked from rbenv and modified for Python.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv&lt;/code&gt; is easy in development. All you need is to create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ruby-version&lt;/code&gt; file in a directory and from that moment the version specified in this file will be used in this particular directory.&lt;/p&gt;

&lt;p&gt;It bundles greatly with &lt;a href=&quot;http://bundler.io/&quot;&gt;Bundler&lt;/a&gt;. In fact, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundler&lt;/code&gt; takes care of &lt;em&gt;gem&lt;/em&gt; management completely. Each installation of &lt;em&gt;Ruby&lt;/em&gt; gets its own installation of bundler. Installation can be configured so that it reuses gems for projects that utilize same Ruby version. Otherwise, gemsets are installed on per-project basis.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv&lt;/code&gt; works by introducing a directory full of small executables called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shims&lt;/code&gt; into your path. The path to this directory looks like this: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/Users/user/.rbenv/shims&lt;/code&gt;. Each &lt;em&gt;shim&lt;/em&gt; is a tiny Bash script that has the exact same name as any Ruby interpreter based tool in your system.&lt;/p&gt;

&lt;p&gt;For example, before calling the actual &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Jekyll&lt;/code&gt; executable, a shim will check the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ruby-version&lt;/code&gt; files and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RBENV_VERSION&lt;/code&gt; environmental variable as well as global &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.rbenv/version&lt;/code&gt; file if other options did not work. &lt;em&gt;Shim&lt;/em&gt; will then pull out a correct version of Jekyll according to your specified Ruby version. To be able to do that, the directory with shims is placed first in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$PATH&lt;/code&gt;. This way any call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Jekyll&lt;/code&gt; in our case will be at first directed to a &lt;em&gt;shim&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let’s take a look at how a user would use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv&lt;/code&gt;.&lt;/p&gt;

&lt;h4 id=&quot;checking-what-ruby-versions-are-available&quot;&gt;Checking what Ruby versions are available&lt;/h4&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;rbenv &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--list&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Alternatively: rbenv install -l&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Result:&lt;/span&gt;
Available versions:
  1.8.5-p52
  1.8.5-p113
  1.8.5-p114
  1.8.5-p115
  1.8.5-p231
  1.8.6
  1.8.6-p36
&lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
  2.5.0-rc1
  2.5.0
  2.6.0-dev
  2.6.0-preview1
&lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
  jruby-9.1.15.0
  jruby-9.1.16.0
  jruby-9.2.0.0-dev
&lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
  ree-1.8.7-2012.01
  ree-1.8.7-2012.02
  topaz-dev&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The whole list is much longer. You can see that beside the versions of the standard interpreter there is a whole bunch of other implementations.&lt;/p&gt;

&lt;h4 id=&quot;installing-ruby-versions&quot;&gt;Installing Ruby versions&lt;/h4&gt;

&lt;p&gt;Any version is installed into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.rbenv directory&lt;/code&gt;. You can later specify which installed version you want to use in your project.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;rbenv &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;2.5.0&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;install-bundler&quot;&gt;Install bundler&lt;/h4&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;bundler&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;install-gems&quot;&gt;Install gems&lt;/h4&gt;

&lt;p&gt;Now, if you have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt; ready, you can perform an installation of all required gems.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;bundle &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h4 id=&quot;choose-ruby-version-for-project&quot;&gt;Choose Ruby version for project&lt;/h4&gt;

&lt;p&gt;The order in which Ruby version is checked by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv&lt;/code&gt; is perfectly described in the docs. It is hard to rephrase it better:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;When you execute a shim, rbenv determines which Ruby version to use by reading it from the following sources, in this order:&lt;/p&gt;

  &lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RBENV_VERSION&lt;/code&gt; environment variable, if specified. You can use the rbenv shell command to set this environment variable in your current shell session.&lt;/p&gt;

  &lt;p&gt;The first &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ruby-version&lt;/code&gt; file found by searching the directory of the script you are executing and each of its parent directories until reaching the root of your filesystem.&lt;/p&gt;

  &lt;p&gt;The first &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ruby-version&lt;/code&gt; file found by searching the current working directory and each of its parent directories until reaching the root of your filesystem. You can modify the .ruby-version file in the current working directory with the rbenv local command.&lt;/p&gt;

  &lt;p&gt;The global &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.rbenv/version&lt;/code&gt; file. You can modify this file using the rbenv global command. If the global version file is not present, rbenv assumes you want to use the “system” Ruby—i.e. whatever version would be run if rbenv weren’t in your path.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;rbenv-installation-on-macos-x-&quot;&gt;Rbenv installation on MacOS X &lt;a name=&quot;macos-x-installation&quot;&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A proper installation of ruby environment manager on MacOS X requires only few steps. Most importantly, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv&lt;/code&gt; has to be installed through &lt;strong&gt;Homebrew&lt;/strong&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv init&lt;/code&gt; command has to be called.&lt;/p&gt;

&lt;h3 id=&quot;step-1&quot;&gt;Step 1&lt;/h3&gt;
&lt;p&gt;Install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;brew install rbenv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note that this also installs ruby-build, so you’ll be ready to install other Ruby versions out of the box.&lt;/p&gt;

&lt;h3 id=&quot;step-2&quot;&gt;Step 2&lt;/h3&gt;
&lt;p&gt;Initialize &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv&lt;/code&gt; on your OS.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rbenv init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;thats-it&quot;&gt;That’s it!&lt;/h3&gt;

&lt;p&gt;Don’t forget to call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rbenv rehash&lt;/code&gt; after installing new gems to give rbenv a change to generate new &lt;em&gt;shims&lt;/em&gt;.&lt;/p&gt;</content><author><name>Vagiz Duseev</name></author><summary type="html">Using a system’s default Ruby interpreter to develop projects and install required gemsets is a sure path to dependency nightmare. Each project needs its own environment independent from others. At the same time, the system’s installation of Ruby should be left intact. This article gives a quick overview of the popular Ruby environment managers and provides a concise installation instruction for a painless setup on MacOS X.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://image.ibb.co/hebX8T/rbenv_vs_rvm.jpg" /><media:content medium="image" url="https://image.ibb.co/hebX8T/rbenv_vs_rvm.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Writing an IntelliJ IDEA plugin to manage window layouts</title><link href="https://duseev.com/articles/writing-intellij-idea-window-layout-plugin/" rel="alternate" type="text/html" title="Writing an IntelliJ IDEA plugin to manage window layouts" /><published>2017-10-20T11:29:01+00:00</published><updated>2017-10-20T11:29:01+00:00</updated><id>https://duseev.com/articles/writing-intellij-idea-window-layout-plugin</id><content type="html" xml:base="https://duseev.com/articles/writing-intellij-idea-window-layout-plugin/">&lt;p&gt;There are many IntelliJ plugins out there. Still, existing SDK documentation
does not cover every development step. Here I describe how a basic IntelliJ
plugin with menu and import/export functionality can be developed.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://image.ibb.co/bvFg2o/plugin_window.jpg&quot; alt=&quot;IntelliJ IDEA plugin to manage window layouts&quot; /&gt;&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;plugins-purpose&quot;&gt;Plugin’s purpose&lt;/h2&gt;
&lt;p&gt;Sometimes, after working on some project for a long enough period, you end up with a
particular window layout that is convenient for you, e.g., console on the right side,
source code on the left, debug window at the top.&lt;/p&gt;

&lt;p&gt;However, if you open the same project on a different machine, the layout you’ve so delicately configured will be lost. Same situation will happen if you want to reuse a layout from some other project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Preserve Layout Plugin&lt;/strong&gt; allows you to export the layout of any
IntelliJ project and then import it back. Export is done using the XML format.&lt;/p&gt;

&lt;h2 id=&quot;about-this-blog-post&quot;&gt;About this blog post&lt;/h2&gt;
&lt;p&gt;I wrote this post to address the issues I encountered during development of
such plugin. As of
current date (Oct 20, 2017), IntelliJ SDK documentation is way far from being
complete. Below is my collection of findings regarding missing pieces.&lt;/p&gt;

&lt;h2 id=&quot;initial-setup&quot;&gt;Initial Setup&lt;/h2&gt;
&lt;p&gt;For initial setup, if you never developed a JetBrains’ plugin, I highly
recommend to go through the &lt;a href=&quot;https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started.html&quot;&gt;&lt;strong&gt;Getting Started&lt;/strong&gt;&lt;/a&gt; section
of IntelliJ Platform SDK.&lt;/p&gt;

&lt;p&gt;You will notice that some sections are missing and greyed out. However, just
by following these
initial setup steps you will get just enough to create your own plugin. It is
important to mention that in order to &lt;strong&gt;debug&lt;/strong&gt; a plugin you will need
the source code of IntelliJ Community Edition checked out and attached to the
project.&lt;/p&gt;

&lt;h2 id=&quot;prototyping&quot;&gt;Prototyping&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;If you are familiar with plugin development for IntelliJ, it is safe to skip
this section.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To play around a little bit, let’s create a very simple plugin with one action.
Just to test the things and confirm that setup is working.&lt;/p&gt;

&lt;p&gt;First thing you might look in to is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plugin.xml&lt;/code&gt; file that contains the
basic configuration of your plugin.&lt;/p&gt;

&lt;p&gt;After filling in the standard information fields like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;vendor&amp;gt;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;version&amp;gt;&lt;/code&gt;
we can jump straight to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;actions&amp;gt;&lt;/code&gt; section.&lt;br /&gt;
Here we can statically position and initialize our actions.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;plugin.xml&lt;/code&gt; is sufficient enough for most needs. However, when doing complex
stuff, we can initialize actions dynamically in Java.&lt;/p&gt;

&lt;p&gt;Let’s position our
action in the Project View popup menu, the one that appears when you
right-click the project name in IDEA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Information tags:&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;id&amp;gt;&lt;/span&gt;com.duseev.intellij.preservelayout&lt;span class=&quot;nt&quot;&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;Preserve Layout Plugin&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;vendor&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;email=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;vagiz@duseev.com&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;url=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://duseev.com&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Duseev.com&lt;span class=&quot;nt&quot;&gt;&amp;lt;/vendor&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Decribing actions:&lt;/strong&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;actions&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;action&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ExportLayout&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.duseev.intellij.preservelayout.LayoutExporter&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;text=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;LayoutExporter&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;description=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;LayoutExporter&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;add-to-group&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;group-id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ProjectViewPopupMenu&quot;&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;anchor=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;after&quot;&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;relative-to-action=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ExternalToolsGroup&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/action&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/actions&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can see that we created a unique &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt; for this action. A class for the
should also be specified. A name and description will be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LayoutExporter&lt;/code&gt;. The
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;add-to-group ...&amp;gt;&lt;/code&gt; tag is the one responsible for tying our action to the
specific menu in IDEA.&lt;/p&gt;

&lt;p&gt;Obviously, we will need a class responsible for this action. Let’s make it
super simple: it will print project’s name to the console. You can
notice that we inherit from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AnAction&lt;/code&gt; class and override the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;actionPerformed&lt;/code&gt; method.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.duseev.intellij.preservelayout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.intellij.openapi.actionSystem.AnAction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.intellij.openapi.actionSystem.AnActionEvent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.intellij.openapi.project.*&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LayoutExporter&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AnAction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

   &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
   &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;actionPerformed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AnActionEvent&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;c1&quot;&gt;// TODO: insert action logic here&lt;/span&gt;
       &lt;span class=&quot;nc&quot;&gt;Project&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
       &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
   &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;After we run this code, IDEA will spawn a new instance of itself with our
plugin installed by default. After we right click on the project (you will need
a project to be created or open) and invoke context menu, we will see that
&lt;em&gt;LayoutExporter&lt;/em&gt; action appeared in this menu. Clicking this action will
print a project name to the console output.&lt;/p&gt;

&lt;p&gt;Of course, we can assign actions to a different menu. For example, in
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;preserve-layout-plugin&lt;/code&gt; actions are added to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WindowMenu&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;window-layout-in-idea&quot;&gt;Window layout in IDEA&lt;/h2&gt;
&lt;p&gt;Stepping into the actual logic, we might ask ourselves where is the layout of
current project stored in IntelliJ IDEA? Turns out it can be stored in &lt;a href=&quot;https://www.jetbrains.com/help/idea/about-projects.html&quot;&gt;two ways&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;directory based project&lt;/code&gt;: in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;workspace.xml&lt;/code&gt; file under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.idea&lt;/code&gt; directory&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;file based project&lt;/code&gt;: in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.iws&lt;/code&gt; file in a project directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JetBrains is planning to &lt;a href=&quot;https://intellij-support.jetbrains.com/hc/en-us/community/posts/206167769/comments/206288985&quot;&gt;deprecate&lt;/a&gt; the file based format.
But we still have to support both options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting current window layout:&lt;/strong&gt;
To obtain current layout state we obtain an instance of the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ToolWindowManagerImpl&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;ToolWindowManagerImpl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mgr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ToolWindowManagerImpl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ToolWindowManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Having the manager, we can now ask it for a current layout. An instance of
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DesktopLayout&lt;/code&gt; will be returned. Then we cas use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DesktopLayout&lt;/code&gt; to return
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org.jdom.Element&lt;/code&gt; representation of itself.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;DesktopLayout&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mgr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLayout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;writeExternal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;layout&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Next step is to save this XML/DOM Element somewhere.&lt;/p&gt;

&lt;h2 id=&quot;file-saver-dialog--savingwriting-a-file&quot;&gt;File Saver dialog &amp;amp; Saving/writing a file&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;File Saving dialog:&lt;/strong&gt;
Somehow, file saving dialog in IntelliJ is invoked in a different way,
comparing to File Chooser. It is also not mentioned yet in documentation.
However, you can come across a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FileSaverDescriptor&lt;/code&gt; class, which when
instantiated and passed to
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FileChooserFactory&lt;/code&gt; as a parameter will produce a correct dialog window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing to a File in IntelliJ:&lt;/strong&gt;
Actual data writing is simple, but not obvious. It is required to execute
“write” operations outside of the main thread. To address this,
IntelliJ provides
a very convenient &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WriteCommandAction&lt;/code&gt; abstraction.&lt;/p&gt;

&lt;p&gt;Besides that, best approach to write a file to a disk is through
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VirtualFile&lt;/code&gt; abstraction layer.&lt;/p&gt;

&lt;p&gt;Let’s take a look at a composed example of logic required to save an XML to
a file in IntelliJ.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;c1&quot;&gt;// Use XML to covert Element to String&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;XMLOutputter&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outputter&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;XMLOutputter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getPrettyFormat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exportContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outputter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;outputString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Describe file saving dialog&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;FileSaverDescriptor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FileSaverDescriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;Save Layout&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;Choose path for layout export...&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;xml&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Obtain a file wrapper from FileChooserFactory&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;VirtualFileWrapper&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wrapper&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FileChooserFactory&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createSaveFileDialog&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBaseDir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;layout.xml&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wrapper&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;VirtualFile&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wrapper&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getVirtualFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Couldn't create new file&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Write to disk outside of main GUI thread&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;WriteCommandAction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Simple&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;VfsUtil&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;saveText&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exportContent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;file-chooser-dialog--importing-layout&quot;&gt;File Chooser dialog &amp;amp; importing layout&lt;/h2&gt;
&lt;p&gt;The best way to select a file in IntelliJ is to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FileChooser&lt;/code&gt; dialog
and the overloaded function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chooseFile&lt;/code&gt; that accepts a callback parameter to
be called after the file is selected. When using this approach IntelliJ is
able to invoke native file chooser dialog window on every platform.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;FileChooser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;chooseFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;FileChooserDescriptorFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createSingleFileDescriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;importLayoutFileToProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCanonicalPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now we need a callback function &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;importLayoutFileToProject()&lt;/code&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;importLayoutFileToProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Project&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parseLayoutFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;applyLayoutToProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// notify user&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As you can notice,
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;importLayoutFileToProject&lt;/code&gt; makes 2 calls to the helping methods.
One to parse the imported XML file, and the other to apply the parsed
layout to IDE. Let’s implement them as well.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;parseLayoutFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Document&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SAXBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;doc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getRootElement&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;applyLayoutToProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Element&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Project&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;ToolWindowManagerImpl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mgr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ToolWindowManagerImpl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ToolWindowManager&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;DesktopLayout&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mgr&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLayout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;readExternal&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Having all that we can now import and export window layout’s of any project.
However, it
would be nice to notify a user about results of the export/import process.&lt;/p&gt;

&lt;h2 id=&quot;intellij-notifications&quot;&gt;IntelliJ Notifications&lt;/h2&gt;
&lt;p&gt;Basic notifications are very easy to fire in IntelliJ:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;Notifications&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Bus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Notification&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Preserve Layout&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Successful Export&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Saved to &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCanonicalPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;NotificationType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;INFORMATION&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here we can change the icon to emphasize the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ERROR&lt;/code&gt; status.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span class=&quot;nc&quot;&gt;Notifications&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Bus&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;notify&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Notification&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Preserve Layout&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;Export Failed&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getMessage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;NotificationType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ERROR&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;make-plugin-runnable-in-every-jetbrains-ide&quot;&gt;Make plugin runnable in every JetBrains IDE&lt;/h2&gt;
&lt;p&gt;Last thing but not the least important. To support every JetBrains IDE it is
required to specify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;depends&amp;gt;&lt;/code&gt; tag in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;plugin&amp;gt;&lt;/code&gt;. More details about
plugin compatibility are available at &lt;a href=&quot;http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html&quot;&gt;SDK docs&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;depends&amp;gt;&lt;/span&gt;com.intellij.modules.lang&lt;span class=&quot;nt&quot;&gt;&amp;lt;/depends&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h3 id=&quot;special-thanks&quot;&gt;Special thanks&lt;/h3&gt;
&lt;p&gt;This plugin has been developed thanks to &lt;a href=&quot;https://github.com/zolotov&quot;&gt;Alexander Zolotov&lt;/a&gt; from JetBrains team.
He provided tons of invaluable tips and advices during the development of this plugin.&lt;/p&gt;</content><author><name>Vagiz Duseev</name></author><summary type="html">There are many IntelliJ plugins out there. Still, existing SDK documentation does not cover every development step. Here I describe how a basic IntelliJ plugin with menu and import/export functionality can be developed.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://image.ibb.co/bvFg2o/plugin_window.jpg" /><media:content medium="image" url="https://image.ibb.co/bvFg2o/plugin_window.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>