aignas blogPublic notesZola2023-05-29T00:00:00+00:00https://anikevicius.lt/atom.xmlPython lazy loading and namespace packages2023-05-29T00:00:00+00:002023-05-29T00:00:00+00:00https://anikevicius.lt/blog/python-lazy-loading-and-namespace-pkgs/<p>Recently I have bumped into a really weird issue, where in one project whilst
trying to use a package <code>pkg</code> (I’ll use this name to avoid using a real project
name) and write tests using the <code>foo</code> backend, that lived in a different
package called <code>pkg_foo</code>. It was not working as intended when testing using the
<code>bazel</code> build system and this post is about the path to root-causing the issue
and arriving at the right conclusions.</p>
<p>TLDR: The source code with tests is in the <a href="https://github.com/aignas/anikevicius.lt/tree/master/content/blog/python_lazy_loading_and_namespace_pkgs/src">github.com/aignas/anikevicius.lt/</a>.</p>
<span id="continue-reading"></span><h1 id="python-and-it-s-lesser-known-features">Python and it’s lesser-known features</h1>
<p>Recently I have learned about two Python features that may help with the dependency hell and I am going to set the context really quickly before moving on with the issue at hand.</p>
<ul>
<li>You can achieve lazy-loading of your modules by using a <code>__getattr__</code>
function based on <a href="https://peps.python.org/pep-0562/">PEP562</a>.</li>
<li>You can have a multiple packages constitute a single top-level module and use
<a href="https://docs.python.org/3/library/pkgutil.html#pkgutil.extend_path">pkgutil.extend_path</a> for telling Python how to make imports work. For
example, the main package providing <code>pkg</code> and <code>pkg.main</code> can be extended by
a <code>pkg_extension</code> package that adds <code>pkg.extension</code> and everything works
magically.</li>
</ul>
<h1 id="the-common-path">The common path</h1>
<p>The common path which is exercised by most of the developers out there is that
the <code>pkg</code> and <code>pkg_foo</code> gets installed into the same <code>virtualenv</code> by the developer
and everything works as intended, because the <code>site-packages</code> directory in that <code>virtualenv</code>
would contain the following subtree:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span>site-packages/
</span><span> pkg
</span><span> __init__.py # contains the __getattr__ function to implement lazy loading
</span><span> main.py
</span><span> pkg_extension/
</span><span> __init__.py # contains pkgutil glue
</span><span> extension/ # contains contents of `pkg_extension` package
</span><span> ...
</span></code></pre>
<p>This means that both of the features will work correctly because the installed
Python package share the same file system layout and usually the
<code>pkg_extension</code> is usually visited after the <code>pkg</code> is visited when trying to
import things from the <code>pkg</code> package.</p>
<p>This means that the minimum working code example for the contents of <code>pkg/__init__.py</code> is:</p>
<pre data-lang="python" style="background-color:#272822;color:#f8f8f2;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#75715e;"># Make `pkg` an namespace package.
</span><span>__path__ </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">__import__</span><span>(</span><span style="color:#e6db74;">"pkgutil"</span><span>).extend_path(__path__, __name__) </span><span style="color:#75715e;"># type: ignore
</span><span>
</span><span>
</span><span>__lazy_imports </span><span style="color:#f92672;">= </span><span>{
</span><span> </span><span style="color:#e6db74;">"foo"</span><span>: (</span><span style="color:#e6db74;">".dir.lib"</span><span>, </span><span style="color:#e6db74;">"foo"</span><span>),
</span><span>}
</span><span>
</span><span>
</span><span style="font-style:italic;color:#f92672;">def </span><span style="color:#66d9ef;">__getattr__</span><span>(</span><span style="font-style:italic;color:#fd971f;">name</span><span>: </span><span style="font-style:italic;color:#66d9ef;">str</span><span>):
</span><span> </span><span style="color:#75715e;"># PEP-562: Lazy loaded attributes on python modules
</span><span> module_path, attr_name </span><span style="color:#f92672;">= </span><span>__lazy_imports.get(name, (</span><span style="color:#e6db74;">""</span><span>, </span><span style="color:#e6db74;">""</span><span>))
</span><span>
</span><span> </span><span style="color:#f92672;">if not </span><span>module_path:
</span><span> </span><span style="color:#f92672;">raise </span><span style="font-style:italic;color:#66d9ef;">AttributeError</span><span>(</span><span style="font-style:italic;color:#66d9ef;">f</span><span style="color:#e6db74;">"module </span><span>{__name__</span><span style="color:#f92672;">!r</span><span>}</span><span style="color:#e6db74;"> has no attribute </span><span>{name</span><span style="color:#f92672;">!r</span><span>}</span><span style="color:#e6db74;">"</span><span>)
</span><span>
</span><span> </span><span style="color:#f92672;">import </span><span>importlib
</span><span>
</span><span> mod </span><span style="color:#f92672;">= </span><span>importlib.import_module(module_path, __name__)
</span><span> </span><span style="color:#f92672;">if </span><span>attr_name:
</span><span> val </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">getattr</span><span>(mod, attr_name)
</span><span> </span><span style="color:#f92672;">else</span><span>:
</span><span> val </span><span style="color:#f92672;">= </span><span>mod
</span><span>
</span><span> </span><span style="color:#75715e;"># Store for next time
</span><span> </span><span style="color:#66d9ef;">globals</span><span>()[name] </span><span style="color:#f92672;">= </span><span>val
</span><span> </span><span style="color:#f92672;">return </span><span>val
</span><span>
</span><span style="color:#75715e;"># We may include extra things below
</span></code></pre>
<p>And the <code>pkg_extension/__init__.py</code> only needs the hooks for the <code>pkgutil</code>:</p>
<pre data-lang="python" style="background-color:#272822;color:#f8f8f2;" class="language-python "><code class="language-python" data-lang="python"><span>__path__ </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">__import__</span><span>(</span><span style="color:#e6db74;">"pkgutil"</span><span>).extend_path(__path__, __name__) </span><span style="color:#75715e;"># type: ignore
</span></code></pre>
<h1 id="when-things-may-go-wrong">When things may go wrong</h1>
<p>In a simple virtual env, everything works as expected all of the time as the
directory traversal is deterministic (citation needed) and we usually hit the
<code>__init__.py</code> file from the <code>pkg</code> before the one from <code>pkg_extension</code>.
We were also careful to name our extensions with easy to find naming scheme and
everything works until we start using a build tool that has a different Python
package layout from the one expected by us.</p>
<p>When using <code>bazel</code> the <code>sys.path</code> order is determined by your build dependency
DAG (Directed Acyclic Graph), and the order of the packages appearing in the
<code>sys.path</code> is not lexicographically sorted (as of 6.2.0 at least). This means
that we may have the <code>pkg_extension</code> before the <code>pkg</code> in our <code>sys.path</code> which
will make it to be visited by the Python import machinery before the
<code>pkg/__init__.py</code> which has the lazy-loading magic.</p>
<p>To test this behaviour I have created a few tests in the <a href="https://github.com/aignas/anikevicius.lt/tree/master/content/blog/python_lazy_loading_and_namespace_pkgs/src">example</a> folder and
decided to go down the rabbit hole. Below is a list of combinations that I
have tested:</p>
<h2 id="pkg-and-pkg-extension-names"><code>pkg</code> and <code>pkg_extension</code> names</h2>
<p>This is working as intended if the <code>pkg</code> appears before <code>pkg_extension</code> in the <code>sys.path</code>.
If we reverse the order of their entries in the <code>sys.path</code>, then the lazy-loaded functions
and the regular function from <code>__init__.py</code> from the <code>pkg</code> package is failing.</p>
<h2 id="pkg-and-extension-pkg-names"><code>pkg</code> and <code>extension_pkg</code> names</h2>
<p>This is working in the same way, as the previous case, the <code>pkg</code> needs to be
before the <code>extension_pkg</code> in the <code>sys.path</code>.</p>
<h2 id="copy-the-lazy-loading-machinery-to-the-extension-pkg-correct-and-use-that">Copy the lazy loading machinery to the <code>extension_pkg_correct</code> and use that</h2>
<p>So what the two data points are telling us is that the <code>lazy-loading</code> ceases to function
when the first thing that gets visited is the <code>__init__.py</code> file without the PEP562 hooks.
So if we copy the PEP562 hooks to the extension file, what happens then?</p>
<p>This, as expected makes the lazy loading work, because the lazy loading works by specifying the absolute import path as such:</p>
<pre data-lang="python" style="background-color:#272822;color:#f8f8f2;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#75715e;"># __path__ manipulation added by bazelbuild/rules_python to support namespace pkgs.
</span><span>__path__ </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">__import__</span><span>(</span><span style="color:#e6db74;">"pkgutil"</span><span>).extend_path(__path__, __name__)
</span><span>
</span><span>
</span><span>__lazy_imports </span><span style="color:#f92672;">= </span><span>{
</span><span> </span><span style="color:#e6db74;">"foo"</span><span>: (</span><span style="color:#e6db74;">"pkg.dir.lib"</span><span>, </span><span style="color:#e6db74;">"foo"</span><span>),
</span><span>}
</span><span>
</span><span>
</span><span style="font-style:italic;color:#f92672;">def </span><span style="color:#66d9ef;">__getattr__</span><span>(</span><span style="font-style:italic;color:#fd971f;">name</span><span>: </span><span style="font-style:italic;color:#66d9ef;">str</span><span>):
</span><span> </span><span style="color:#75715e;"># PEP-562: Lazy loaded attributes on python modules
</span><span> module_path, attr_name </span><span style="color:#f92672;">= </span><span>__lazy_imports.get(name, (</span><span style="color:#e6db74;">""</span><span>, </span><span style="color:#e6db74;">""</span><span>))
</span><span>
</span><span> </span><span style="color:#f92672;">if not </span><span>module_path:
</span><span> </span><span style="color:#f92672;">raise </span><span style="font-style:italic;color:#66d9ef;">AttributeError</span><span>(</span><span style="font-style:italic;color:#66d9ef;">f</span><span style="color:#e6db74;">"module </span><span>{__name__</span><span style="color:#f92672;">!r</span><span>}</span><span style="color:#e6db74;"> has no attribute </span><span>{name</span><span style="color:#f92672;">!r</span><span>}</span><span style="color:#e6db74;">"</span><span>)
</span><span>
</span><span> </span><span style="color:#f92672;">import </span><span>importlib
</span><span>
</span><span> mod </span><span style="color:#f92672;">= </span><span>importlib.import_module(module_path, __name__)
</span><span> </span><span style="color:#f92672;">if </span><span>attr_name:
</span><span> val </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">getattr</span><span>(mod, attr_name)
</span><span> </span><span style="color:#f92672;">else</span><span>:
</span><span> val </span><span style="color:#f92672;">= </span><span>mod
</span><span>
</span><span> </span><span style="color:#75715e;"># Store for next time
</span><span> </span><span style="color:#66d9ef;">globals</span><span>()[name] </span><span style="color:#f92672;">= </span><span>val
</span><span> </span><span style="color:#f92672;">return </span><span>val
</span></code></pre>
<p>Notice, the contents of <code>__lazy_imports</code>, which now has the absolute import
paths rather than relative ones as in the first snippet.</p>
<p>However it seems that we cannot import the function <code>fizz</code> that happens to be
in the <code>pkg/__init__.py</code> at the end of the file in the main package.</p>
<h1 id="conclusion">Conclusion</h1>
<p>It seems that having lazy imports using <a href="https://peps.python.org/pep-0562/">PEP562</a> and supporting
<a href="https://docs.python.org/3/library/pkgutil.html#pkgutil.extend_path">pkgutil.extend_path</a> usage to split the package into multiple parts does not
together. It may seem somewhat weird if one wants to do that, because if you
can depend on the lazy-import machinery, maybe you don’t need to split your
packages anymore. On the other hand, everything works as expected if you have
only a single <code>site-packages</code> location where you install your packages, which
is almost always the case for regular Python users or Python installations
inside containers.</p>
Python lazy loading and namespace packages example2023-05-29T00:00:00+00:002023-05-29T00:00:00+00:00https://anikevicius.lt/blog/python_lazy_loading_and_namespace_pkgs/src/readme/<h1 id="namespace-packages-work-correctly">Namespace packages work correctly</h1>
<p>This is a test reproducing the behaviour of Apache airflow packages that
I have observed at dayjob. It seems that the <code>lazy-import</code> feature is
not working as intended if the package that is doing the lazy import is
not the first item in the <code>sys.path</code> list that <code>bazel</code> is constructing.</p>
<p>See <a href="./tests/BUILD.bazel">tests/BUILD.bazel</a> for documentation of the test cases.</p>
Debugging sway and i3status-rs2022-09-19T00:00:00+00:002022-09-19T00:00:00+00:00https://anikevicius.lt/blog/sway-i3status-rs/<p>Recently I have been toying around with the <code>sway</code> compositor on Wayland in
order to have a more lightweight setup with better support for keyboard-only
interaction. However, because I have not used Arch Linux on my desktop for some
time, I am re-learning a few things as I go as well.</p>
<span id="continue-reading"></span>
<p>I have followed the documentation on setting up <code>i3status-rs</code> on my machine and
I pasted the initial config for <code>sway</code> from the website:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span>bar {
</span><span> font pango:Hack, FontAwesome 11
</span><span> ...
</span><span>}
</span></code></pre>
<p>I realized later, was not working correctly and was showing 漢字 instead of
font awesome icons. The reason why it took me so long to realize something was
not working correctly was because I was also setting up correct <code>locale</code> setting
in order to be able to render Japanese fonts correctly.</p>
<p>It seems that <a href="https://github.com/greshake/i3status-rust#integrate-it-into-i3sway">the
author</a> has
also specified that it would be good to ensure that <code>FontAwesome</code> is pointing
to the right font on one’s system, so let’s do that:</p>
<pre data-lang="bash" style="background-color:#272822;color:#f8f8f2;" class="language-bash "><code class="language-bash" data-lang="bash"><span>$ fc-match FontAwesome
</span><span>DejaVuSans.ttf: </span><span style="color:#e6db74;">"DejaVu Sans" "Book
</span></code></pre>
<p>That’s unexpected, let’s see what <code>pacman</code> has installed on my system:</p>
<pre data-lang="bash" style="background-color:#272822;color:#f8f8f2;" class="language-bash "><code class="language-bash" data-lang="bash"><span>$ pacman</span><span style="font-style:italic;color:#fd971f;"> -Ql</span><span> ttf-font-awesome </span><span style="color:#f92672;">| </span><span>rg fa-
</span><span>ttf-font-awesome /usr/share/fonts/TTF/fa-brands-400.ttf
</span><span>ttf-font-awesome /usr/share/fonts/TTF/fa-regular-400.ttf
</span><span>ttf-font-awesome /usr/share/fonts/TTF/fa-solid-900.ttf
</span><span>ttf-font-awesome /usr/share/fonts/TTF/fa-v4compatibility.ttf
</span></code></pre>
<p>Then we can check what names of the fonts we can use by:</p>
<pre data-lang="bash" style="background-color:#272822;color:#f8f8f2;" class="language-bash "><code class="language-bash" data-lang="bash"><span>$ fc-match</span><span style="font-style:italic;color:#fd971f;"> -a </span><span style="color:#f92672;">| </span><span>rg </span><span style="color:#e6db74;">'^fa-'
</span><span>fa-brands-400.ttf: </span><span style="color:#e6db74;">"Font Awesome 6 Brands" "Regular"
</span><span>fa-regular-400.ttf: </span><span style="color:#e6db74;">"Font Awesome 6 Free" "Regular"
</span><span>fa-v4compatibility.ttf: </span><span style="color:#e6db74;">"Font Awesome v4 Compatibility" "Regular"
</span><span>fa-solid-900.ttf: </span><span style="color:#e6db74;">"Font Awesome 6 Free" "Solid"
</span></code></pre>
<p>This means that we should get <code>sway</code> working by:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span>bar {
</span><span> font pango:Hack, "Font Awesome 6 Free" 11
</span><span> ...
</span><span>}
</span></code></pre>
<p>After restarting everything works correctly.</p>
bazel repo: Starting a monorepo2020-06-07T00:00:00+00:002020-06-07T00:00:00+00:00https://anikevicius.lt/blog/bazel-01/<p>I love monorepos and it was not the case prevously but I do think that monorepos shine in scenarios where:</p>
<ul>
<li>The code is tightly coupled together.</li>
<li>One needs to ensure that resources on dev-tooling are efficiently spent.</li>
<li>Migrations can be completed atomically.</li>
</ul>
<p>Bazel is a really good build system, which allows reproducible and reasonably
fast builds and the build system is configured with starlark, which can be used
to write custom build rules, which allow to do linting or ensure consistency in
packaging, etc.</p>
<span id="continue-reading"></span>
<p>I use <code>bazel</code> at work and I do not want to have to remember how to use two
build systems or build my <code>make</code> targets and maintain them across multiple
repositories. For this reason I decide to attempt to create a monorepo for
myself, which would leverage <code>bazel</code> to:</p>
<ul>
<li>Write in multiple languages:
<ul>
<li>Rust</li>
<li>Go</li>
<li>Shell</li>
</ul>
</li>
<li>Have common packaging for all.</li>
<li>Have reproducible builds and leverage caching in between the builds.</li>
</ul>
<p>In a few hours I could complete a repository setup, which:</p>
<ul>
<li>Uses <code>direnv</code> to setup dev environment.</li>
<li>Uses <code>bazelisk</code> to download the predefined <code>bazel</code> version.</li>
<li>Setup a custom <code>shellcheck</code> rule which lints all files with <code>.sh</code> extension in my <code>tools</code> directory.</li>
<li>Setup <code>gazelle</code> for upcoming go code.</li>
<li>Setup <code>buldifier</code> to ensure that my <code>BUILD.bazel</code> files are formatted.</li>
<li>Setup a CI script to ensure that my repository is clean.</li>
</ul>
<p>See it at <a href="https://github.com/aignas/c">aignas/c</a>.</p>
<p>Future ideas:</p>
<ul>
<li>Setup Travis CI or GitHub actions.</li>
<li>A toy <code>go</code> CLI to iron out wrinkles in the <code>gazelle</code> and CI setup.</li>
<li>A toy <code>rust</code> CLI to try out <code>cargo-raze</code>.</li>
<li>Add tests for my <code>shell</code> scripts to test out <code>sh_test</code> rules.</li>
<li>Generalize the <code>shellcheck</code> <code>bazel</code> rule and put it into a separate repository.</li>
<li>Write a <code>shfmt</code> rule.</li>
<li><code>bazel</code> packaging rule for ArchLinux.</li>
<li>Make the setup work on all OSes:
<ul>
<li>The <code>bazelisk</code> bootstrap script should check the OS before downloading <code>bazelisk</code> binary.</li>
<li><code>shellcheck</code> rule should detect the OS and download the appropriate <code>shellcheck</code> binary for the host architecture.</li>
</ul>
</li>
</ul>
<p>Disclaimer: whilst I do intend to continue experimenting with this and post future updates here, I have other hobbies and it may take some time before I advance this experiment further.</p>
Unattended updates in Debian2020-04-19T00:00:00+00:002020-04-19T00:00:00+00:00https://anikevicius.lt/blog/unattended-updates/<p>Because Debian is really stable and has high security patching standards, you
should have auto-upgrades on if you are running <code>stable</code>. This is one of the
things that you cannot have with more bleeding edge distros like
<a href="https://archlinux.org">ArchLinux</a>.</p>
<p>Read through <a href="https://wiki.debian.org/UnattendedUpgrades">official docs</a> in order to set it up now.</p>
<p>TLDR:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span>$ sudo apt-get install unattended-upgrades
</span><span>$ sudo dpkg-reconfigure -plow unattended-upgrades
</span><span>$ echo 'APT::Periodic::Enable "1";
</span><span>APT::Periodic::Update-Package-Lists "1";
</span><span>APT::Periodic::Download-Upgradeable-Packages "1";
</span><span>APT::Periodic::Unattended-Upgrade "1";
</span><span>APT::Periodic::AutocleanInterval "21";
</span><span>APT::Periodic::Verbose "2";' | sudo tee /etc/apt/apt.conf.d/02periodic
</span></code></pre>
<p>And then to ensure that things are configured somewhat well:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span>$ sudo unattended-upgrade -d
</span></code></pre>
Good places to visit in Vilnius if you like food and culture2019-06-21T00:00:00+00:002019-06-21T00:00:00+00:00https://anikevicius.lt/blog/vilnius-recommendations/<p>Sometimes people ask me where to go when they are first visiting Vilnius.
Here are some of the suggestions if you are interested to visit this city and
enjoy art/food/drinks/coffee.</p>
<p>This list could definitely be added to, but it is a good start.</p>
<h1 id="art-and-culture">Art and culture</h1>
<ul>
<li><a href="https://goo.gl/maps/vyqD8d7AhMw">Mo museum</a>: Lithuanian modern art museum.</li>
</ul>
<h1 id="coffee">Coffee</h1>
<ul>
<li><a href="https://goo.gl/maps/ey9JRYnxDwdc8eZ6A">TasteMap</a>:</li>
<li><a href="https://goo.gl/maps/nYB75GMwbUa3f73U7">Cofmos</a>:</li>
<li><a href="https://goo.gl/maps/aWgFXCKrdD9HjFzv8">Kavos reikalai</a>:</li>
<li><a href="https://goo.gl/maps/nm6NdDKpAjD2">Brew</a>: Nice place to get coffee.</li>
<li><a href="https://goo.gl/maps/yoF1zysmeDQ2">Sugamour</a>: A nice place to get some cake.</li>
<li><a href="https://goo.gl/maps/Cjm815fuBFM2">Italala</a>: Another place to get good coffee.</li>
</ul>
<h1 id="food">Food</h1>
<ul>
<li><a href="https://goo.gl/maps/PWtwvDHxqLB2">La Boheme</a>: You can get some good wine here.</li>
<li><a href="https://goo.gl/maps/FkYfppUpJ552">Kamikadze</a>: Good Japanese food, but might need a reservation:</li>
<li><a href="https://goo.gl/maps/xmrL4qAMV6M2">Sofa de Pancho</a>: Good Mexican food and good Mezcal:</li>
<li><a href="https://goo.gl/maps/QkWaRP6Foym">Theobromine</a>: Good chocolate place, they are making excellent pralines with different flavours.</li>
<li><a href="https://goo.gl/maps/Nqvjp6X8zLA2">Ertlio namas</a>: Old Lithuanian cuisine.</li>
<li><a href="https://goo.gl/maps/RCZBQD9Suaq">Le Butcher</a>: Reasonably good burgers.</li>
</ul>
<h1 id="drinks">Drinks</h1>
<ul>
<li><a href="https://goo.gl/maps/yHiKjA4FPFK2">Duokle angelams (Angel’s Share)</a>: If good single malt whiskey and cocktails is your thing:</li>
<li><a href="https://goo.gl/maps/ypGr3nwXqVN2">Rum Room</a>: If rum is something you would rather fancy. Also gives food IIRC:</li>
<li><a href="https://goo.gl/maps/qY1jMAmCvrp">Alaus biblioteka (Beer Library)</a>: If you want to try really good local (and non-local beer).</li>
</ul>
git-prompt-rs2018-12-10T00:00:00+00:002018-12-10T00:00:00+00:00https://anikevicius.lt/projects/git-prompt/<p>As a way to learn <a href="https://www.rust-lang.org/">Rust</a> I decided to write a small
git prompt. I use it everyday myself and I strive it to be an example of how
fast something like this can be.</p>
<h2 id="goals">Goals</h2>
<ul>
<li>Fast: Rust’s excellent benchmarking tooling helps with that.</li>
<li>Read-only: Rust’s immutable by default helps me to create a read-only interface to the git-repository.</li>
<li>Cross-platform: Currently tested on Mac and Linux.</li>
<li>Shell agnostic: It’s a binary it runs everywhere.</li>
</ul>
<h2 id="features">Features</h2>
<ul>
<li>
<p>When counting the commit difference between the remote and the local clone it
will default to the <code>master</code> on the remote if a branch with the same name is
not found. The default is customizable.</p>
</li>
<li>
<p>It will make sure that the last character of the prompt is a space. Some
shells break because of this.</p>
</li>
</ul>
<h2 id="screencast">Screencast</h2>
<p><a href="https://asciinema.org/a/RlvQkQ57HZ6Pcw7pNlvuLAfjd" target="_blank"><img src="https://asciinema.org/a/RlvQkQ57HZ6Pcw7pNlvuLAfjd.svg" width="600"/></a></p>
Makefile: adding prerequisites to external targets2018-12-06T00:00:00+00:002018-12-06T00:00:00+00:00https://anikevicius.lt/blog/makefiles-adding-prerequisites/<p>Consider that you have a situation where you have a build system using <code>make</code>
heavily. And you want to hook into one of the targets such that before the
target is built, <code>make</code> builds the prerequisite you are interested in.</p>
<span id="continue-reading"></span>
<p>The following <code>Makefile</code> illustrates what we have at hand:</p>
<pre data-lang="makefile" style="background-color:#272822;color:#f8f8f2;" class="language-makefile "><code class="language-makefile" data-lang="makefile"><span style="color:#a6e22e;">api</span><span style="color:#f92672;">: </span><span style="color:#e6db74;">dep
</span><span> </span><span style="color:#ae81ff;">@</span><span style="color:#66d9ef;">echo </span><span style="color:#e6db74;">"-- building API --"
</span><span>
</span><span style="color:#a6e22e;">dep</span><span style="color:#f92672;">:
</span><span> </span><span style="color:#ae81ff;">@</span><span style="color:#66d9ef;">echo </span><span style="color:#e6db74;">"-- building dep --"
</span><span>
</span><span style="color:#75715e;">#######################################
</span><span style="color:#75715e;"># The above is defined somewhere else #
</span><span style="color:#75715e;">#######################################
</span><span>
</span><span style="color:#75715e;"># My rules
</span></code></pre>
<p>Using the make file yields us:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span>$ make api
</span><span>-- building dep --
</span><span>-- building API --
</span></code></pre>
<p>Now what we want is to build docs before <code>dep</code> is built.
It turns out that this can be achieved simply by adding the following to your
<code>Makefile</code>:</p>
<pre data-lang="makefile" style="background-color:#272822;color:#f8f8f2;" class="language-makefile "><code class="language-makefile" data-lang="makefile"><span style="color:#a6e22e;">dep</span><span style="color:#f92672;">: </span><span style="color:#e6db74;">docs
</span><span style="color:#a6e22e;">docs</span><span style="color:#f92672;">:
</span><span> </span><span style="color:#ae81ff;">@</span><span style="color:#66d9ef;">echo </span><span style="color:#e6db74;">"-- building docs --"
</span></code></pre>
<p>Then when we execute the same command, we get:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span>$ make api
</span><span>-- building docs --
</span><span>-- building dep --
</span><span>-- building API --
</span></code></pre>
<p>This technique can be really useful to add prerequisites for targets without
modifying them. Read more about multiple rules for make targets in the
<a href="https://www.gnu.org/software/make/manual/html_node/Multiple-Rules.html">GNU make documentation</a>.</p>
git-prompt-rs: Type driven development in Rust2018-11-25T00:00:00+00:002018-11-25T00:00:00+00:00https://anikevicius.lt/blog/git-prompt-04/<p>This post is from a series of posts about writing a small application in Rust
to display information about a particular git repository.</p>
<ul>
<li><a href="https://anikevicius.lt/blog/git-prompt-01/">Part 1: Writing MVP</a></li>
<li><a href="https://anikevicius.lt/blog/git-prompt-02/">Part 2: Logging</a></li>
<li><a href="https://anikevicius.lt/blog/git-prompt-03/">Part 3: Testing the UI and color schemes</a></li>
</ul>
<p>In this part I try to explore the problem by relying on the Rust’s compiler and
the sum types.</p>
<span id="continue-reading"></span><h1 id="rewriting-the-type-driven-way">Rewriting, the type-driven way.</h1>
<p>I am really happy with what I have learned during this exploration of the rust
language and I think that it is has been a really nice journey. However, there
was one more thing which I wanted to try out, which I did not manage to do with
the previous version of the CLI.</p>
<p>Type driven development is only really possible when there are no <code>NULL</code> values
and you can explore how the types/possible failure states constrain the high
level structure of the domain logic of your program. With functional languages like <code>F#</code> or <code>Haskell</code> you can leverage the fact that the types can be inferred automatically, but with Rust one can start testing out how to work with the following developing flow:</p>
<ul>
<li>Investigate the structure via the type system (e.g. <code>Result</code>, <code>Option</code>).</li>
<li>Implement the minimum happy paths.</li>
<li>Stabilize the implementations by adding unit tests.</li>
</ul>
<p>I have recently started working with <code>go</code> in my day-to-day programming at work and I realized
that one could also start reusing concepts like:</p>
<ul>
<li>Accept interfaces, return structs.</li>
<li>Use the trait system to make external structs implement the locally defined traits.</li>
</ul>
<p>All of the things mentioned above means that I could write the whole program in
a slightly different manner than before. You can find the code on <a href="https://github.com/aignas/git-prompt-rs">github</a>.</p>
<h1 id="type-driven">Type driven</h1>
<p>What I like the most about rust is the sum-types and how they change the way
one needs to think about the domain. The fact that you get <code>Option<T></code> or
<code>Result<T, E></code> from a particular function means that you will need to do some
error handling or potentially think about how to recover at the place where the
error occurs if you do not want to propagate the <code>Result</code> type up the stack.
The similar is true for <code>go</code> as well, which helps one to write relatively
stable code early on.</p>
<p>However, where this really shines is when you start modelling your program as a
motion of data. At the end of the day the whole CLI design is all about the
data. This means that researching the domain of the problem with data
structures where one passes them around is a reasonably natural thing to do.
This has the following implications:</p>
<ul>
<li>
<p>One can decouple the software from the parts of the code which interact with
the outside world.</p>
</li>
<li>
<p>One can more easily bundle the data that needs to be moved together, which
makes the interaction of different code pieces much easier.</p>
</li>
</ul>
<h1 id="discoveries">Discoveries</h1>
<p>There were some interesting discoveries along the way and the most important
realisation was the fact that the whole application becomes much easier to test
the functional Model-View architecture is being used. Since this is not a
long-running application, there is no need to think how the application is
going to be updated, but the potential for generalisation is there.</p>
<p>Testing was another thing that surprised me. I have a lot of
scientific/engineering related background and the code that one writes there
most of the times deals with data transformations involving complex algorithms
and mathematical formulae. The data input into the system most of the times
constitutes only a small part of the system.</p>
<p>However, when one deals with querying of stateful systems and the part that
matters is the representation of the data in those stateful systems, the hard
part becomes either the UI, or the queries of the stateful systems and not as
much the data transformations. This means that creating a seam early on to not
rely on mocks to test the system is really good.</p>
git-prompt-rs: Generating test data for UI tuning2018-11-11T00:00:00+00:002018-11-11T00:00:00+00:00https://anikevicius.lt/blog/git-prompt-03/<p>This post is from a series of posts about writing a small application in Rust to display information about a particular git repository.</p>
<ul>
<li><a href="https://anikevicius.lt/blog/git-prompt-01/">Part 1: Writing MVP</a></li>
<li><a href="https://anikevicius.lt/blog/git-prompt-02/">Part 2: Logging</a></li>
</ul>
<p>I have finished at the MVP stage (part 1) of the prompt after adding the logging (part 2) so in this post I summarize the rest of features:</p>
<ul>
<li><code>git</code> branch status</li>
<li><code>git</code> local status</li>
</ul>
<span id="continue-reading"></span><h1 id="git-local-status">Git local status</h1>
<p>At the moment we have a simple program that can print a branch name into the
<code>stdout</code>. It does provide some information, but it is nowhere near the desired
outcome.</p>
<p>If we look again at the example I have given in [part 1][part-1] of the series, we have information about the current repo index status, such as:</p>
<ul>
<li>number of changed tracked files</li>
<li>number of untracked files</li>
<li>number of files that need merging</li>
</ul>
<p><img src="https://anikevicius.lt/blog/git-prompt-03/od-prompt-img.png" alt="zsh-git-prompt-example" /></p>
<p>These are quite useful things to know when one is working on a project stored
in <code>git</code> and that is what we are going to implement next.</p>
<p>I have done some refactoring since the last time and the overall program now
looks as follows:</p>
<pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#75715e;">// .. imports
</span><span>
</span><span style="font-style:italic;color:#66d9ef;">type </span><span>R</span><span style="color:#f92672;"><</span><span>T</span><span style="color:#f92672;">> = </span><span style="font-style:italic;color:#66d9ef;">Result</span><span><T, </span><span style="font-style:italic;color:#66d9ef;">String</span><span>>;
</span><span>
</span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">main</span><span>() {
</span><span> </span><span style="color:#75715e;">// .. parsing of the CLI arguments
</span><span>
</span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> output </span><span style="color:#f92672;">= </span><span style="color:#66d9ef;">get_output</span><span>(path);
</span><span> debug!(</span><span style="color:#e6db74;">"Result: {:?}"</span><span>, output);
</span><span> print!(</span><span style="color:#e6db74;">"</span><span style="color:#ae81ff;">{} </span><span style="color:#e6db74;">"</span><span>, output.</span><span style="color:#66d9ef;">unwrap_or</span><span>(</span><span style="font-style:italic;color:#66d9ef;">String</span><span>::new()))
</span><span>}
</span><span>
</span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">get_output</span><span>(</span><span style="font-style:italic;color:#fd971f;">path</span><span>: </span><span style="color:#f92672;">&</span><span style="font-style:italic;color:#66d9ef;">str</span><span>) -> R<</span><span style="font-style:italic;color:#66d9ef;">String</span><span>> {
</span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> repo </span><span style="color:#f92672;">= </span><span>Repository::discover(path).</span><span style="color:#66d9ef;">or</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(</span><span style="color:#e6db74;">"no repo found"</span><span>))</span><span style="color:#f92672;">?</span><span>;
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(format!(</span><span style="color:#e6db74;">"</span><span style="color:#ae81ff;">{}</span><span style="color:#e6db74;">"</span><span>, </span><span style="color:#66d9ef;">get_branch_name</span><span>(</span><span style="color:#f92672;">&</span><span>repo)</span><span style="color:#f92672;">?</span><span>))
</span><span>}
</span><span>
</span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">get_branch_name</span><span>(</span><span style="font-style:italic;color:#fd971f;">repo</span><span>: </span><span style="color:#f92672;">&</span><span>Repository) -> R<</span><span style="font-style:italic;color:#66d9ef;">String</span><span>> {
</span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> head </span><span style="color:#f92672;">=</span><span> repo.</span><span style="color:#66d9ef;">head</span><span>().</span><span style="color:#66d9ef;">or</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(</span><span style="color:#e6db74;">"failed to get HEAD"</span><span>))</span><span style="color:#f92672;">?</span><span>;
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(head.</span><span style="color:#66d9ef;">shorthand</span><span>().</span><span style="color:#66d9ef;">unwrap_or</span><span>(</span><span style="color:#e6db74;">"unknown"</span><span>).</span><span style="color:#66d9ef;">to_owned</span><span>())
</span><span>}
</span><span>
</span></code></pre>
<p>The idea is to encapsulate all of the logic of constructing the status in one
function and main is only responsible for the interaction with the outside
world.</p>
<h1 id="some-notes-on-the-data">Some notes on the data</h1>
<p>It is important to note, that when we receive the status of the files in the
git repository, each item in the list may have multiple flags associated to it.
For example, we could have staged the changes in a particular file (e.g.
<code>main.rs</code>) and then modified it and then I would expect the status to show,
that there is one staged file and one file that has modifications. That is why
we should not think that the sum of all changed items in the status list is the
sum of items that have been changed.</p>
<p>I am going to take some inspiration from Olivier and define them as:</p>
<ul>
<li><code>✔ </code> repository is clean</li>
<li><code>●n</code> there are n files that have staged changes</li>
<li><code>○n</code> there are n files that have unstaged changes</li>
<li><code>✗n</code> there are n files that have unmerged changes</li>
<li><code>+n</code> there are n files that are untracked</li>
</ul>
<p>The implementation is not one of the most succinct, so I’ll spare you from
having all of it inline, especially since I have outline the main things how it
is going to be structured.</p>
<h1 id="branch-status">Branch status</h1>
<p>I still need to implement some way to have branch status and after some thinking I decided to go with the following approach:</p>
<ul>
<li>As a user I want to see differences between the remote branch in <code>origin</code>.</li>
<li>If there is no upstream branch in <code>origin</code>, do the comparison with the <code>origin/master</code>.</li>
<li>If I am cherry-picking, rebasing, merging, print one of instead:
<ul>
<li><code>cherry-pick</code></li>
<li><code>rebase</code></li>
<li><code>merge</code></li>
</ul>
</li>
</ul>
<p>Thus the result should look as:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span><branch> <branch-status> <local-status>
</span></code></pre>
<p>Let’s use the <a href="https://docs.rs/git2/0.7.5/git2/enum.RepositoryState.html">repository state</a> and implement the last
part of the list by creating a function which maps the state to a string:</p>
<pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#75715e;">// ...
</span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> state </span><span style="color:#f92672;">= match</span><span> repo.</span><span style="color:#66d9ef;">state</span><span>() {
</span><span> git2::RepositoryState::Merge </span><span style="color:#f92672;">=> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(</span><span style="color:#e6db74;">"merge"</span><span>),
</span><span> git2::RepositoryState::Rebase
</span><span> </span><span style="color:#f92672;">| </span><span>git2::RepositoryState::RebaseInteractive
</span><span> </span><span style="color:#f92672;">| </span><span>git2::RepositoryState::RebaseMerge </span><span style="color:#f92672;">=> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(</span><span style="color:#e6db74;">"rebase"</span><span>),
</span><span> git2::RepositoryState::RevertSequence </span><span style="color:#f92672;">=> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(</span><span style="color:#e6db74;">"revert"</span><span>),
</span><span> git2::RepositoryState::CherryPick </span><span style="color:#f92672;">| </span><span>git2::RepositoryState::CherryPickSequence </span><span style="color:#f92672;">=> </span><span>{
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(</span><span style="color:#e6db74;">"cherry-pick"</span><span>)
</span><span> }
</span><span> </span><span style="color:#f92672;">_ => </span><span style="font-style:italic;color:#66d9ef;">None</span><span>,
</span><span> };
</span><span>
</span><span> </span><span style="color:#f92672;">if </span><span style="font-style:italic;color:#66d9ef;">let Some</span><span>(s) </span><span style="color:#f92672;">=</span><span> state {
</span><span> </span><span style="color:#f92672;">return </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(format!(</span><span style="color:#e6db74;">" </span><span style="color:#ae81ff;">{}</span><span style="color:#e6db74;">"</span><span>, s));
</span><span> }
</span><span style="color:#75715e;">// ...
</span></code></pre>
<p>As you see, we want to return the state of the branch, if we are doing
something <em>special</em> on not do anything else. The only odd thing about this is
that <code>rustfmt</code> is formatting the code in an interesting way for the
<code>cherry-pick</code> case, but I can live with this.</p>
<h1 id="colours">Colours</h1>
<p>Up until now everything was white on black on my terminal. This means that it
may require more time to quickly notice the state of the git repo. That is why
I decided to use the <a href="https://github.com/mackwic/colored">colored crate</a>, which makes it very easy
to define color schemes by using simple names of the colours. I also hope to
expose this as a configuration parameters for the user.</p>
<p>However, fine-tuning of the defaults may require some tinkering and that is why
I decided to write some helper routines, which would print me some examples.
For that I implemented a naïve random data generator using
<a href="https://docs.rs/rand/0.5.3/rand/trait.Rng.html#method.choose">rng::choose</a>. The final test can be seen below:</p>
<p><img src="https://anikevicius.lt/blog/git-prompt-03/test.png" alt="final test" /></p>
<p>This is really useful to test happy paths and see how things look like with
various data.</p>
<p>Continue to:</p>
<ul>
<li><a href="https://anikevicius.lt/blog/git-prompt-04/">Part 4: type driven rewrite</a></li>
</ul>
git-prompt-rs: diagnostics and logging2018-11-10T00:00:00+00:002018-11-10T00:00:00+00:00https://anikevicius.lt/blog/git-prompt-02/<p>This post is from a series of posts about writing a small application in Rust to display information about a particular git repository.</p>
<ul>
<li><a href="https://anikevicius.lt/blog/git-prompt-01/">Part 1: Writing MVP</a></li>
</ul>
<p>Yesterday I finished with a reasonably good starting point and I thought that
I would not talk about diagnostics, but after finding out how good <code>rust</code>
tool-chain is at this, I wanted to share some thoughts.</p>
<span id="continue-reading"></span>
<p>Yes, some simple CLIs like ours probably don’t need an option to do heavy
diagnostics on it. However, when we do run into problems and don’t have a good
way to introspect your programs, I am going to waste more time than I want.
That is why it makes sense to start doing it as early as possible.</p>
<h1 id="macros-to-the-rescue">Macros to the rescue</h1>
<p>Using the tips from <a href="https://rust-lang-nursery.github.io/rust-cookbook/development_tools/debugging/log.html">rust cookbook</a> one can get started really quickly.
Add the following to the dependency section in your <code>Cargo.toml</code>:</p>
<pre data-lang="toml" style="background-color:#272822;color:#f8f8f2;" class="language-toml "><code class="language-toml" data-lang="toml"><span style="color:#f92672;">log </span><span>= </span><span style="color:#e6db74;">"0.4"
</span><span style="color:#f92672;">env_logger </span><span>= </span><span style="color:#e6db74;">"0.5"
</span></code></pre>
<p>and run:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span>$ cargo update
</span></code></pre>
<p>Then follow the instructions to get the <code>env_logger</code> set up.</p>
<p>Then you can enable debug logging for your own program by simply running like:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span>$ RUST_LOG=git_prompt cargo run -- ${YOUR_PATH_TO_GIT_REPO}
</span></code></pre>
<p>If you want all logs, including other packages, you can do this by:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span>$ RUST_LOG=debug cargo run -- ${YOUR_PATH_TO_GIT_REPO}
</span></code></pre>
<p>This gives you all the logs from all the packages and the compiler. This can be really useful tricky situations, which I am probably not going to encounter here, but it will definitely useful in the future.</p>
<p>Things I really like:</p>
<ul>
<li><code>debug!</code> and other related macros can do really clever stuff in order to
limit the impact of the logging.</li>
<li>standard way to do logging through macros.</li>
<li>not requiring to do dependency injection for logging. It is a cross-cutting
concern and as a result it may pollute your object factory functions.</li>
</ul>
<p>Continue to:</p>
<ul>
<li><a href="https://anikevicius.lt/blog/git-prompt-03/">Part 3: generating test data</a></li>
<li><a href="https://anikevicius.lt/blog/git-prompt-04/">Part 4: type driven rewrite</a></li>
</ul>
git-prompt-rs: Writing an MWP2018-11-09T00:00:00+00:002018-11-09T00:00:00+00:00https://anikevicius.lt/blog/git-prompt-01/<p>As an attempt to learn Rust and type-driven development using it I am going
to show you how to write a small Rust CLI, which gives you status about the
current repository. I wanted a small CLI to update my prompt for <a href="https://github.com/redox-os/ion">ion</a>
and <a href="https://ohmyz.sh/">zsh</a> and I wanted it to be as fast as possible with the possibility
to extend in case I want to display more information.</p>
<span id="continue-reading"></span>
<p>For <code>zsh</code> there already is a nice solution made by Olivier Verdier <a href="https://github.com/olivierverdier/zsh-git-prompt" title="github repository">on github</a>.</p>
<p><img src="https://anikevicius.lt/blog/git-prompt-01/od-prompt-img.png" alt="zsh-git-prompt-example" /></p>
<p>I think that it has a nice way of printing information and that is why I decided to use it as a starting point.</p>
<h1 id="motivation">Motivation</h1>
<p>Why do I want to have such prompt written in Rust? There are at least a few benefits from technical standpoint:</p>
<ul>
<li>It runs everywhere</li>
<li>Everything is immutable by default, I don’t want to change the repository, I only want to query it.</li>
<li>I can easily parallelize certain bits, which means that it is faster.</li>
<li>I can have tests and type safety.</li>
</ul>
<h1 id="high-level-problem-description">High level problem description</h1>
<p>So this is a small program, which can be moddeled as below CLI interface if we wanted to pass all of the arguments to it as mixture of positional and optional arguments:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span>PROGRAM [<display options>] dir
</span></code></pre>
<p>Where the display switches may be:</p>
<ul>
<li>should it print a new line character at the end</li>
<li>print error if it occurs or not</li>
<li>read current directory from environment</li>
<li>various switches which could toggle the amount of the information being printed:
<ul>
<li>current branch</li>
<li>ahead/behind count</li>
<li>git status summary</li>
<li>current repository state (e.g. merge, cherry-pick, rebase)</li>
</ul>
</li>
</ul>
<p>And the options, which may have values associated with them</p>
<ul>
<li>color-scheme specification</li>
</ul>
<h1 id="mvp">MVP</h1>
<p>In order for it to become useful straight away it needs to be able to:</p>
<ul>
<li>discover if we are currently in a git repository.</li>
<li>if yes, print a human-readable form of the current revision.</li>
</ul>
<p>Some cross-functional requirements:</p>
<ul>
<li>It might be nice if we can have some color for the branch.</li>
<li>If we cannot find a git repo, we should exit straight away.</li>
<li>We need to be able debug easily in case we have issues.</li>
<li>It should never break <code>ion</code> or <code>zsh</code> shells.</li>
</ul>
<p>The API we want to implement is:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span>PROGRAM [path]
</span></code></pre>
<h1 id="tools">Tools</h1>
<p>I am going to develop in my dotfiles repository, because I want the plugns to
be easy to install for me. Since everything will be contained in a single
folder, I will be able to export the git repository easily if needed later.</p>
<p>We start of by creating a <code>.gitignore</code></p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span># ignore build files
</span><span>target/
</span><span>
</span><span># only store the definitions
</span><span>Cargo.lock
</span></code></pre>
<p>And then we can create a new application using the excellent <code>rust</code> tool-chain:</p>
<pre style="background-color:#272822;color:#f8f8f2;"><code><span>$ mkdir git-prompt-rs
</span><span>$ cargo init --bin --edition 2018 git-prompt-rs
</span><span>$ cd git-prompt-rs
</span></code></pre>
<p>Since I am coding with <a href="https://github.com/neovim/neovim">neovim</a>, I am going to define the following maps
in my vimrc:</p>
<pre data-lang="vim" style="background-color:#272822;color:#f8f8f2;" class="language-vim "><code class="language-vim" data-lang="vim"><span style="color:#f92672;">augroup</span><span> rust_settings
</span><span> </span><span style="color:#66d9ef;">autocmd</span><span>!
</span><span> </span><span style="color:#66d9ef;">autocmd </span><span>FileType rust </span><span style="color:#66d9ef;">let </span><span>g:rustfmt_autosave = </span><span style="color:#ae81ff;">1
</span><span> </span><span style="color:#66d9ef;">autocmd </span><span>FileType rust </span><span style="color:#66d9ef;">nnoremap </span><span style="font-style:italic;color:#66d9ef;"><leader></span><span>cr :!cargo run --</span><span style="font-style:italic;color:#66d9ef;"><end>
</span><span> </span><span style="color:#66d9ef;">autocmd </span><span>FileType rust </span><span style="color:#66d9ef;">nnoremap </span><span style="font-style:italic;color:#66d9ef;"><leader></span><span>ct :!cargo test</span><span style="font-style:italic;color:#66d9ef;"><cr>
</span><span> </span><span style="color:#66d9ef;">autocmd </span><span>FileType rust </span><span style="color:#66d9ef;">nnoremap </span><span style="font-style:italic;color:#66d9ef;"><leader></span><span>ct :!cargo bench</span><span style="font-style:italic;color:#66d9ef;"><cr>
</span><span> </span><span style="color:#66d9ef;">autocmd </span><span>FileType rust </span><span style="color:#66d9ef;">nnoremap </span><span style="font-style:italic;color:#66d9ef;"><leader></span><span>cu :!cargo update</span><span style="font-style:italic;color:#66d9ef;"><cr>
</span><span style="color:#f92672;">augroup</span><span> END
</span></code></pre>
<p>This will help me to check the status often and iterate quickly.</p>
<p>Now <code>cargo run</code> should print <code>Hello, world!</code>, which means that we can continue.</p>
<h1 id="code">Code</h1>
<p>We are going to use the excellent <a href="https://docs.rs/crate/git2/0.7.5">git2</a> to interact with the
repository and it has a really nice API.</p>
<p>First, let’s print path if it is passed in or print the current directory,
which we can get from the environment. If that fails we are going to use the
shorthand <code>"."</code> for the current directory.</p>
<pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>std::env;
</span><span>
</span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">main</span><span>() {
</span><span> </span><span style="color:#75715e;">// get all arguments
</span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> args: </span><span style="font-style:italic;color:#66d9ef;">Vec</span><span><</span><span style="font-style:italic;color:#66d9ef;">String</span><span>> </span><span style="color:#f92672;">= </span><span>env::args().</span><span style="color:#66d9ef;">collect</span><span>();
</span><span>
</span><span> </span><span style="color:#75715e;">// get the path as a first optional positional argument
</span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> path </span><span style="color:#f92672;">=</span><span> args.</span><span style="color:#66d9ef;">get</span><span>(</span><span style="color:#ae81ff;">1</span><span>)
</span><span> .</span><span style="color:#66d9ef;">map</span><span>(|</span><span style="font-style:italic;color:#fd971f;">p</span><span>| p.</span><span style="color:#66d9ef;">to_string</span><span>()) </span><span style="color:#75715e;">// do a copy
</span><span> .</span><span style="color:#66d9ef;">or</span><span>(env::var(</span><span style="color:#e6db74;">"PWD"</span><span>).</span><span style="color:#66d9ef;">ok</span><span>()) </span><span style="color:#75715e;">// try getting the path from PWD env var
</span><span> .</span><span style="color:#66d9ef;">unwrap_or</span><span>(</span><span style="font-style:italic;color:#66d9ef;">String</span><span>::from(</span><span style="color:#e6db74;">"."</span><span>)); </span><span style="color:#75715e;">// fallback to "."
</span><span>
</span><span> println!(</span><span style="color:#e6db74;">"Current path is: </span><span style="color:#ae81ff;">{:?}</span><span style="color:#e6db74;">"</span><span>, path);
</span><span>}
</span></code></pre>
<p>If we run <code>cargo run</code> it we get:</p>
<pre data-lang="sh" style="background-color:#272822;color:#f8f8f2;" class="language-sh "><code class="language-sh" data-lang="sh"><span>Current path is: </span><span style="color:#e6db74;">"/home/ia/src/github/dotfiles/ion/plugins/git-prompt-rs"
</span></code></pre>
<p>If we run <code>cargo run -- ${HOME}</code> it we get:</p>
<pre data-lang="sh" style="background-color:#272822;color:#f8f8f2;" class="language-sh "><code class="language-sh" data-lang="sh"><span>Current path is: </span><span style="color:#e6db74;">"/home/ia"
</span></code></pre>
<p>However, I wanted to have a nice way to add parameters and the
<a href="https://docs.rs/crate/clap/2.32.0">clap</a> crate seems to be a really nice way to do that, so the final
code looks slightly more complex, but more functional:</p>
<pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">extern crate</span><span> clap;
</span><span style="color:#f92672;">use </span><span>clap::App;
</span><span style="color:#f92672;">use </span><span>clap::Arg;
</span><span>
</span><span>
</span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">main</span><span>() {
</span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> matches </span><span style="color:#f92672;">= </span><span>App::new(</span><span style="color:#e6db74;">"git-prompt"</span><span>)
</span><span> .</span><span style="color:#66d9ef;">version</span><span>(</span><span style="color:#e6db74;">"v0.1"</span><span>)
</span><span> .</span><span style="color:#66d9ef;">author</span><span>(</span><span style="color:#e6db74;">"aignas@github"</span><span>)
</span><span> .</span><span style="color:#66d9ef;">about</span><span>(</span><span style="color:#e6db74;">"Prints your git prompt info fast!"</span><span>)
</span><span> .</span><span style="color:#66d9ef;">arg</span><span>(Arg::with_name(</span><span style="color:#e6db74;">"PATH"</span><span>)
</span><span> .</span><span style="color:#66d9ef;">help</span><span>(</span><span style="color:#e6db74;">"Optional path to use for getting git info"</span><span>)
</span><span> .</span><span style="color:#66d9ef;">index</span><span>(</span><span style="color:#ae81ff;">1</span><span>)
</span><span> .</span><span style="color:#66d9ef;">default_value</span><span>(</span><span style="color:#e6db74;">"."</span><span>))
</span><span> .</span><span style="color:#66d9ef;">get_matches</span><span>();
</span><span>
</span><span> println!(</span><span style="color:#e6db74;">"Using path: </span><span style="color:#ae81ff;">{}</span><span style="color:#e6db74;">"</span><span>, matches.</span><span style="color:#66d9ef;">value_of</span><span>(</span><span style="color:#e6db74;">"PATH"</span><span>).</span><span style="color:#66d9ef;">unwrap</span><span>())
</span><span>}
</span></code></pre>
<p>Next, we can extend the code so that it prints if we are in a git repo.</p>
<pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#f92672;">use </span><span>git2::Repository;
</span><span>
</span><span style="color:#75715e;">// ...
</span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> path </span><span style="color:#f92672;">=</span><span> matches.</span><span style="color:#66d9ef;">value_of</span><span>(</span><span style="color:#e6db74;">"PATH"</span><span>).</span><span style="color:#66d9ef;">unwrap</span><span>();
</span><span> </span><span style="color:#f92672;">if </span><span>Repository::discover(path).</span><span style="color:#66d9ef;">is_ok</span><span>() {
</span><span> println!(</span><span style="color:#e6db74;">"</span><span style="color:#ae81ff;">{}</span><span style="color:#e6db74;"> is in a git repo"</span><span>, path)
</span><span> } </span><span style="color:#f92672;">else </span><span>{
</span><span> println!(</span><span style="color:#e6db74;">"</span><span style="color:#ae81ff;">{}</span><span style="color:#e6db74;"> is not in a git repo"</span><span>, path)
</span><span> }
</span><span>}
</span></code></pre>
<p>Adding the following function finally completes the puzzle:</p>
<pre data-lang="rust" style="background-color:#272822;color:#f8f8f2;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#66d9ef;">type </span><span>R</span><span style="color:#f92672;"><</span><span>T</span><span style="color:#f92672;">> = </span><span style="font-style:italic;color:#66d9ef;">Result</span><span><T, </span><span style="font-style:italic;color:#66d9ef;">String</span><span>>;
</span><span>
</span><span style="color:#75715e;">// ...
</span><span style="font-style:italic;color:#66d9ef;">fn </span><span style="color:#a6e22e;">get_branch_name</span><span>(</span><span style="font-style:italic;color:#fd971f;">path</span><span>: </span><span style="color:#f92672;">&</span><span style="font-style:italic;color:#66d9ef;">str</span><span>) -> R<</span><span style="font-style:italic;color:#66d9ef;">String</span><span>> {
</span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> repo </span><span style="color:#f92672;">= </span><span>Repository::discover(path)
</span><span> .</span><span style="color:#66d9ef;">or</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(</span><span style="color:#e6db74;">"failed to find a repo for the given path"</span><span>))</span><span style="color:#f92672;">?</span><span>;
</span><span> </span><span style="font-style:italic;color:#66d9ef;">let</span><span> head </span><span style="color:#f92672;">=</span><span> repo
</span><span> .</span><span style="color:#66d9ef;">head</span><span>()
</span><span> .</span><span style="color:#66d9ef;">or</span><span>(</span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(</span><span style="color:#e6db74;">"failed to get HEAD"</span><span>))</span><span style="color:#f92672;">?</span><span>;
</span><span>
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(head
</span><span> .</span><span style="color:#66d9ef;">shorthand</span><span>()
</span><span> .</span><span style="color:#66d9ef;">unwrap_or</span><span>(</span><span style="color:#e6db74;">"unknown"</span><span>)
</span><span> .</span><span style="color:#66d9ef;">to_owned</span><span>())
</span><span>}
</span></code></pre>
<p>There is a final bit of making the diagnostics easier, which could be done by:</p>
<ul>
<li>setting an env var if a specific parameter is defined.</li>
<li>creating a trait for our result type, which does logging when unwrapping.</li>
<li>Using that for unwraps.</li>
</ul>
<p>But that can be left as a practice for the reader.</p>
<p>That is how we got the following prompt:</p>
<p><img src="https://anikevicius.lt/blog/git-prompt-01/mwp.png" alt="final result" /></p>
<p>Continue to:</p>
<ul>
<li><a href="https://anikevicius.lt/blog/git-prompt-02/">Part 2: logging and diagnostics</a></li>
<li><a href="https://anikevicius.lt/blog/git-prompt-03/">Part 3: generating test data</a></li>
<li><a href="https://anikevicius.lt/blog/git-prompt-04/">Part 4: type driven rewrite</a></li>
</ul>