<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>A Work in Progress ...</title><link href="https://thibaud.space/" rel="alternate"/><link href="https://thibaud.space/feeds/all.atom.xml" rel="self"/><id>https://thibaud.space/</id><updated>2026-10-16T00:00:00+02:00</updated><entry><title>Reasons to love Python: Validating Dataclasses (Part 1)</title><link href="https://thibaud.space/2026/10/reasons-to-love-python-validating-dataclasses-part-1.html" rel="alternate"/><published>2026-10-16T00:00:00+02:00</published><updated>2026-10-07T23:06:32+02:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2026-10-16:/2026/10/reasons-to-love-python-validating-dataclasses-part-1.html</id><summary type="html">&lt;p&gt;I love Python. Sure it’s not the fastest language in the world, and the package management can sometimes be painful, but I still love it.&lt;/p&gt;
&lt;p&gt;I often reach for Python when developing the scripts that wrap systems together. Read a configuration file, process that data and do something with it.&lt;/p&gt;
&lt;p&gt;I find myself reaching for a Pydantic &lt;span class="docutils literal"&gt;BaseModel&lt;/span&gt; but I don’t want to create a virtualenv just for the script (&lt;span class="docutils literal"&gt;uv&lt;/span&gt; script mode nonwithstanding), and so I just make it a dataclass and spend the next 20 minutes fighting type errors in my data.&lt;/p&gt;
&lt;p&gt;What if there was a better way? What if I could re-implement just enough of a &lt;span class="docutils literal"&gt;BaseModel&lt;/span&gt; in little enough code that I can just paste it into the script?&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I love Python. Sure it&amp;#8217;s not the fastest language in the world, and the package management can sometimes be painful, but I still love&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I often reach for Python when developing the scripts that wrap systems together. Read a configuration file, process that data and do something with&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;I find myself reaching for a Pydantic &lt;span class="docutils literal"&gt;BaseModel&lt;/span&gt; but I don&amp;#8217;t want to create a virtualenv just for the script (&lt;span class="docutils literal"&gt;uv&lt;/span&gt; script mode nonwithstanding), and so I just make it a dataclass and spend the next 20 minutes fighting type errors in my&amp;nbsp;data.&lt;/p&gt;
&lt;p&gt;What if there was a better way? What if I could re-implement just enough of a &lt;span class="docutils literal"&gt;BaseModel&lt;/span&gt; in little enough code that I can just paste it into the&amp;nbsp;script?&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;Python is marvelous language. It is full of features that feel like they must be intrinsics of the compiler, but if you peek under the hood, the machinery is there, in all it&amp;#8217;s sometimes hacky glory, ready to be&amp;nbsp;inspected.&lt;/p&gt;
&lt;p&gt;Dataclasses are one of them. Add a few annotations, and hey-presto! The magic is performed and your bare class now has an &lt;span class="docutils literal"&gt;__init__&lt;/span&gt;, a &lt;span class="docutils literal"&gt;__str__&lt;/span&gt;, even &lt;span class="docutils literal"&gt;__eq__&lt;/span&gt; if you ask for&amp;nbsp;it!&lt;/p&gt;
&lt;section id="unpacking-yaml-into-a-dataclass"&gt;
&lt;h2&gt;Unpacking &lt;span class="caps"&gt;YAML&lt;/span&gt; into a&amp;nbsp;dataclass&lt;/h2&gt;
&lt;p&gt;Let&amp;#8217;s start with a simple example: we want to define a model, and populate it with data from a &lt;span class="docutils literal"&gt;.yaml&lt;/span&gt;&amp;nbsp;file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TextIO&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;yaml&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TextIO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;person.yaml&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So far so good. The script opens the data file, loads it into a &lt;span class="docutils literal"&gt;dict&lt;/span&gt; and uses &lt;span class="docutils literal"&gt;**&lt;/span&gt; to splat it into &lt;span class="docutils literal"&gt;Person.__init__&lt;/span&gt;, the constructor generated by the &lt;span class="docutils literal"&gt;dataclass&lt;/span&gt;&amp;nbsp;decorator.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;load-dataclass.py
&lt;span class="go"&gt;Person(name=&amp;#39;Thibaud&amp;#39;, age=35)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Un fortunately, this only checks that the data file contains the right fields, not that the fields have the correct types. If we add an extra field, &lt;span class="docutils literal"&gt;Person.__init__&lt;/span&gt; receives an extra argument and we get an&amp;nbsp;exception:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Thibaud&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gt"&gt;Traceback (most recent call last):&lt;/span&gt;
&lt;span class="gr"&gt;File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1, in &amp;lt;module&amp;gt;&lt;/span&gt;
&lt;span class="gr"&gt;TypeError&lt;/span&gt;: &lt;span class="n"&gt;Person.__init__() missing 1 required positional argument: &amp;#39;age&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And if we are missing a&amp;nbsp;field:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Thibaud&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nationality&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gt"&gt;Traceback (most recent call last):&lt;/span&gt;
&lt;span class="gr"&gt;File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1, in &amp;lt;module&amp;gt;&lt;/span&gt;
&lt;span class="gr"&gt;TypeError&lt;/span&gt;: &lt;span class="n"&gt;Person.__init__() got an unexpected keyword argument &amp;#39;nationality&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And if we specify all the arguments, but one of them is the wrong type we do not get an&amp;nbsp;error.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;None of your business!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;Person(name=12, age=&amp;#39;None of your business!&amp;#39;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="validating-scalar-fields"&gt;
&lt;h2&gt;Validating Scalar&amp;nbsp;Fields&lt;/h2&gt;
&lt;p&gt;To ensure that the value that we assign to each field has the correct type, we can introspect the dataclass to read out the type hint for each field, and then check that the value has correct type (or can be casted to the correct&amp;nbsp;type).&lt;/p&gt;
&lt;p&gt;Python gives us a very convenient &lt;a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.get_type_hints"&gt;get_type_hints&lt;/a&gt; function for this that takes care of the nitty gritty details, hands us a dictionary of names, and the associated&amp;nbsp;annotations.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_type_hints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_coerce&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;
    &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# If the value has the correct type, move on&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;

    &lt;span class="c1"&gt;# Otherwise, try to cast it to the correct type&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Tell the checker the class accepts the raw value.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Invalid value &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s2"&gt; for &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;. &amp;quot;&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Expected &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;.&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hydrate_dataclass&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;is_dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;hints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_type_hints&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Skip fields that are not present in the source dictionary.&lt;/span&gt;
        &lt;span class="c1"&gt;# When constructing the dataclass, these will become missing&lt;/span&gt;
        &lt;span class="c1"&gt;# arguments.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_coerce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Look for fields that are not defined in the dataclass.&lt;/span&gt;
    &lt;span class="c1"&gt;# Allows us to make a better error message than the TypeError.&lt;/span&gt;
    &lt;span class="n"&gt;unknown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Unknown fields: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Build the dataclass from the dictionary and return it&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we run this script with a data file that has all correct types, the dataclass is built as before. If the data file has the wrong types though, we get nice error&amp;nbsp;messages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;hydrate_dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Thibaud&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;age&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Not saying!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="gt"&gt;Traceback (most recent call last):&lt;/span&gt;
&lt;span class="gr"&gt;File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1, in &amp;lt;module&amp;gt;&lt;/span&gt;
&lt;span class="gr"&gt;File &amp;quot;hydrate_dataclass.py&amp;quot;, line 29, in hydrate_dataclass&lt;/span&gt;
&lt;span class="gr"&gt;    raise ValueError(err) from None&lt;/span&gt;
&lt;span class="gr"&gt;ValueError&lt;/span&gt;: &lt;span class="n"&gt;Invalid value &amp;#39;Not saying!&amp;#39; for &amp;#39;age&amp;#39;. Expected &amp;#39;int&amp;#39;.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="converting-enums-on-the-fly"&gt;
&lt;h2&gt;Converting Enums on the&amp;nbsp;Fly&lt;/h2&gt;
&lt;p&gt;With what we have so far, we can validate scalar types, but what about enumerations? Can we magically convert values from our input files into the correct enumeration&amp;nbsp;members?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TeamRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StrEnum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;Principal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Team Principal&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;Driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Driver&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;PitCrew&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;PitCrew&amp;quot;&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TeamRole&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In python, enumerations are classes derived from the &lt;span class="docutils literal"&gt;Enum&lt;/span&gt; class. A simple &lt;span class="docutils literal"&gt;is_subclass(x, Enum)&lt;/span&gt; check is enough to detect&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;Once we find an enumeration, we look up it&amp;#8217;s members, and return the correct&amp;nbsp;enumerator.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_coerce&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;
    &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# If the value has the correct type, move on&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;issubclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# The enumerator in the data should be a string,&lt;/span&gt;
        &lt;span class="c1"&gt;# and it should be an enumerator on the Enum.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__members__&lt;/span&gt;
        &lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# Enumerator not found. Raise an error.&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Invalid value &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s2"&gt; for &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;. &amp;quot;&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Expected one of : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;, &amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__members__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="c1"&gt;# Otherwise, try to cast it to the correct type&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Tell the checker the class accepts the raw value.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Invalid value &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s2"&gt; for &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;. &amp;quot;&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Expected &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;.&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That&amp;#8217;s all it took, one &lt;span class="docutils literal"&gt;is_subclass&lt;/span&gt; check and we&amp;#8217;re spitting out&amp;nbsp;enumerations!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;hydrate_dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Thibaud&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;role&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;PitCrew&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="go"&gt;Person(role=&amp;lt;TeamRole.PitCrew: &amp;#39;PitCrew&amp;#39;&amp;gt;, name=&amp;#39;Thibaud&amp;#39;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we try to pass in a value that is not a valid enumerator for the enumeration, we get a descriptive&amp;nbsp;error.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;hydrate_dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Marcus&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;role&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Boss&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="gt"&gt;Traceback (most recent call last):&lt;/span&gt;
&lt;span class="gr"&gt;File &amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;, line 1, in &amp;lt;module&amp;gt;&lt;/span&gt;
&lt;span class="gr"&gt;File &amp;quot;hydrate_dataclass.py&amp;quot;, line 37, in hydrate_dataclass&lt;/span&gt;
&lt;span class="gr"&gt;    raise ValueError(err) from None&lt;/span&gt;
&lt;span class="gr"&gt;ValueError&lt;/span&gt;: &lt;span class="n"&gt;Invalid &amp;#39;Boss&amp;#39; for &amp;#39;role&amp;#39;. Expected one of: Principal, Driver, PitCrew&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="loading-nested-dataclasses"&gt;
&lt;h2&gt;Loading Nested&amp;nbsp;Dataclasses&lt;/h2&gt;
&lt;p&gt;Validating one dataclass is a nice start, but sometimes our data is nested, and better represented as a series of nested&amp;nbsp;dataclasses.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Volunteer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What happens if we try to load this with our &lt;span class="docutils literal"&gt;hydrate_dataclass&lt;/span&gt;&amp;nbsp;function?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;hydrate_dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Volunteer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;identity&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Thibaud&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;
&lt;span class="go"&gt;Volunteer(&lt;/span&gt;
&lt;span class="go"&gt;    identity=Person(name={&amp;#39;name&amp;#39;: &amp;#39;Thibaud&amp;#39;}),&lt;/span&gt;
&lt;span class="go"&gt;    email=&amp;#39;hello@example.com&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That looks good at first sight. But wait, why do we have &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;name={&amp;#8216;name&amp;#8217;:&lt;/span&gt; &amp;#8216;Thibaud&amp;#8217;}&lt;/span&gt; instead of just &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;name=&amp;#8217;Thibaud&amp;#8217;&lt;/span&gt;&lt;/span&gt;?&lt;/p&gt;
&lt;p&gt;Our code is smart enough to detect enumerations, and special case them, but it doesn&amp;#8217;t (yet) know anything about dataclasses. Since &lt;span class="docutils literal"&gt;isinstance(value, field_type)&lt;/span&gt; returns false, the value is converted&amp;nbsp;by&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which is functionally equivalent to&amp;nbsp;running&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Thibaud&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When what we need to do&amp;nbsp;is&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Thibaud&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It&amp;#8217;s an easy fix, we just need to special case dataclasses. Once again, Python gives us exactly what we need: &lt;span class="docutils literal"&gt;is_dataclass&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_coerce&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;
    &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# If the value has the correct type, move on&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;issubclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# The enumerator in the data should be a string,&lt;/span&gt;
        &lt;span class="c1"&gt;# and it should be an enumerator on the Enum.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__members__&lt;/span&gt;
        &lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# Enumerator not found. Raise an error.&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Invalid value &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s2"&gt; for &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;. &amp;quot;&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Expected one of : &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;, &amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__members__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Invalid &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s2"&gt; for &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;. Expected dict.&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hydrate_dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="c1"&gt;# Otherwise, try to cast it to the correct type&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Tell the checker the class accepts the raw value.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Invalid value &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s2"&gt; for &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;. &amp;quot;&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Expected &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field_type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;.&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And with that, we can load nested&amp;nbsp;dataclasses:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;hydrate_dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Volunteer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;identity&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Thibaud&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;
&lt;span class="go"&gt;Volunteer(&lt;/span&gt;
&lt;span class="go"&gt;    identity=Person(name=&amp;#39;Thibaud&amp;#39;),&lt;/span&gt;
&lt;span class="go"&gt;    email=&amp;#39;hello@example.com&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="the-full-script"&gt;
&lt;h2&gt;The Full&amp;nbsp;Script&lt;/h2&gt;
&lt;p&gt;In about 50 lines of code, we can validate a fairly useful set of dataclasses. We can&amp;#8217;t handle lists, dictionaries, optional values, and unions yet, but they are surprisingly easy to&amp;nbsp;implement.&lt;/p&gt;
&lt;p&gt;That will be for a future&amp;nbsp;post.&lt;/p&gt;
&lt;p&gt;In the meantime, you can find the full script that we have worked through in this post&amp;nbsp;here:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://gitlab.com/comkieffer/comkieffer.com/-/blob/master/content/examples/python-dataclasses/hydrate_dataclass_dataclasses.py"&gt;hydrate_dataclass.py&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
</content><category term="misc"/><category term="python"/></entry><entry><title>The case against Makefiles</title><link href="https://thibaud.space/2026/10/the-case-against-makefiles.html" rel="alternate"/><published>2026-10-05T00:00:00+02:00</published><updated>2026-10-06T22:47:37+02:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2026-10-05:/2026/10/the-case-against-makefiles.html</id><summary type="html">&lt;p&gt;You don’t need a Makefile. Whatever your use-case, there is a better tool.&lt;/p&gt;
&lt;p&gt;Makefiles are archaic technology, living on by force of inertia and notalgia. The syntax is opaque and the error messages are unhelpful. It’s time to move on.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;You don&amp;#8217;t need a Makefile. Whatever your use-case, there is a better&amp;nbsp;tool.&lt;/p&gt;
&lt;p&gt;Makefiles are archaic technology, living on by force of inertia and notalgia. The syntax is opaque and the error messages are unhelpful. It&amp;#8217;s time to move&amp;nbsp;on.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;As far as I can see, there are two remaining use cases for Makefiles in the&amp;nbsp;wild:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;building C and C++&amp;nbsp;code;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;running arbitrary tasks that have dependencies between each&amp;nbsp;other.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Makefiles are no longer the best solution for either of these use&amp;nbsp;cases,&lt;/p&gt;
&lt;section id="use-case-1-building-c-and-c-code"&gt;
&lt;h2&gt;Use-case 1: Building C and C++&amp;nbsp;code&lt;/h2&gt;
&lt;p&gt;Makefiles are the original build system. The &lt;span class="docutils literal"&gt;make&lt;/span&gt; program was born in 1976 at Bell Labs, out of a frustration with existing build scripts (piles of &lt;span class="docutils literal"&gt;sh&lt;/span&gt;). The concepts it pionneered (_e.g._ the build graph) are still the basis of modern build systems, but the ergonomics of build systems have improved a lot since&amp;nbsp;then.&lt;/p&gt;
&lt;section id="aside-build-scripts-makefiles-and-other-build-systems"&gt;
&lt;h3&gt;Aside: Build scripts, makefiles, and other build&amp;nbsp;systems&lt;/h3&gt;
&lt;p&gt;Consider a script that builds a pile of source files (without using any fancy shell features that would have been unavailable in&amp;nbsp;1976)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-Wall&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Compile the individual source files to object files&lt;/span&gt;
cc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$CFLAGS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;main.o&lt;span class="w"&gt; &lt;/span&gt;main.c
cc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$CFLAGS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;lib.o&lt;span class="w"&gt; &lt;/span&gt;lib.c

&lt;span class="c1"&gt;# Link the object files into an executable&lt;/span&gt;
cc&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt; &lt;/span&gt;main.o&lt;span class="w"&gt; &lt;/span&gt;lib.o
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Forget to add a &lt;span class="docutils literal"&gt;cc&lt;/span&gt; command to build a source file? Now you&amp;#8217;re linking with an old version of the file that you compiled by&amp;nbsp;hand.&lt;/p&gt;
&lt;p&gt;Using modern &lt;span class="docutils literal"&gt;bash&lt;/span&gt; features, you could improve the script&amp;nbsp;significantly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-Wall&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;SRCS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;main.c lib.c&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;OBJS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SRCS&lt;/span&gt;&lt;span class="p"&gt;/.c/.o&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SRCS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;cc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$CFLAGS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="p"&gt;/.c/.o&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

cc&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;app&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$OBJS&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Using a bash variable expansion to replace &lt;span class="docutils literal"&gt;.c&lt;/span&gt; with &lt;span class="docutils literal"&gt;.o&lt;/span&gt; allows us to use re-use the list of sources in both the compile commands and the linker command. For a small example program, this approach works, but it doesn&amp;#8217;t scale&amp;nbsp;well.&lt;/p&gt;
&lt;p&gt;An equivalent makefile would&amp;nbsp;be&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-Wall
&lt;span class="nv"&gt;SRCS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;main.c&lt;span class="w"&gt; &lt;/span&gt;lib.c
&lt;span class="nv"&gt;OBJS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;SRCS:.c&lt;span class="o"&gt;=&lt;/span&gt;.o&lt;span class="k"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;OBJS&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;CC&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;OBJS&lt;span class="k"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;%.o&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;%.&lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;CC&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;CFLAGS&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;$&amp;lt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The similarities are quite obvious. The most significant difference between the script and the makfile is what we describe. The script is an imperative procedure: it lays out the steps that need to be accomplished to build the final binary, and executes them all in sequence, every time. The makefile describes the dependencies between inputs and outputs. This allows the makefile to skip steps that are redundant because the input has not&amp;nbsp;changed.&lt;/p&gt;
&lt;p&gt;The cost of this change is syntax. The build script is quite readable but the makefile is full of _magic_ tokens. What are &lt;span class="docutils literal"&gt;$&amp;#64;&lt;/span&gt;, and &lt;span class="docutils literal"&gt;$&amp;lt;&lt;/span&gt;? To an experienced practitioner, they are obvious. For me, they require a trip to google each time (or if I actually remember, a trip into &lt;em&gt;The Sum of All Knowledge&lt;/em&gt;, my knowledgebase&amp;nbsp;in`Obsidian`_).&lt;/p&gt;
&lt;p&gt;Compare this with an equivalent CMake&amp;nbsp;script&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cmake_minimum_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;VERSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;3.10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;add_executable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;main.c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;lib.c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Without knowing anything about CMake, the goal of the script is quite clear. The knowledge of how to actually build an executable or a library is hidden which is a shame, but the intent is&amp;nbsp;clear.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="make-s-mistake"&gt;
&lt;h3&gt;Make&amp;#8217;s&amp;nbsp;mistake&lt;/h3&gt;
&lt;p&gt;The fundamental mistake of Make is that it describes the edges of the build graph, not the nodes. It describes the operations required to transform one file into another, but never describes the goal of the&amp;nbsp;transformation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nf"&gt;%.o&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;%.&lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;CC&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;CFLAGS&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$&amp;lt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To build a file, we say that there exists a node &lt;span class="docutils literal"&gt;%.c&lt;/span&gt; and a node &lt;span class="docutils literal"&gt;%.o&lt;/span&gt;, and that to go from &lt;span class="docutils literal"&gt;%.c&lt;/span&gt; to &lt;span class="docutils literal"&gt;%.o&lt;/span&gt;, it is necessary to apply &lt;span class="docutils literal"&gt;$(&lt;span class="caps"&gt;CC&lt;/span&gt;) $(&lt;span class="caps"&gt;CFLAGS&lt;/span&gt;) &lt;span class="pre"&gt;-c&lt;/span&gt; &lt;span class="pre"&gt;-o&lt;/span&gt; $&amp;#64; $&amp;lt;&lt;/span&gt; to &lt;span class="docutils literal"&gt;%.c&lt;/span&gt;. A no point does this say anything about what we are trying to&amp;nbsp;achieve.&lt;/p&gt;
&lt;p&gt;In CMake, to build an executable, we use &lt;span class="docutils literal"&gt;add_executable(app main.c lib.c)&lt;/span&gt;. This describes the end goal, not the transformation. This is not to say that CMake is the perfect build-system, far from that, but it is at least easier to&amp;nbsp;follow.&lt;/p&gt;
&lt;p&gt;Build-systems that describe intent, and not transformations, have one property that most Makefiles in the wild lack: they work reliably when run in parallel. Most makefiles I have had the misfortune of encountering fail when run in parallel. To build reliably, build scripts&amp;nbsp;become&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="k"&gt;$(&lt;/span&gt;nproc&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="k"&gt;$(&lt;/span&gt;nproc&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="k"&gt;$(&lt;/span&gt;nproc&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;all
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is definitely a &lt;em&gt;skill issue&lt;/em&gt;. But a tool too complicated for the average C programmer inside a boring company is no longer a good&amp;nbsp;tool.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="re-inventing-the-wheel-with-make"&gt;
&lt;h3&gt;Re-inventing the wheel with&amp;nbsp;make&lt;/h3&gt;
&lt;p&gt;Make aficionados love to tell new victims how great their makefiles are: they are invariably better than whatever sub-par build system they have begrudgingly accepted to use at their&amp;nbsp;workplace.&lt;/p&gt;
&lt;p&gt;However, if my experience is representative, their makefiles still suffer from the edges-not-nodes issue discussed above, but more problematically: they have accumulated just as much complexity as other build systems, whilst having written none of the documentation that would be required to explain their bespoke build system to&amp;nbsp;others.&lt;/p&gt;
&lt;p&gt;Need to add a new library target? Need to add a custom target for code-generation, doxygen, a formatter, a linter, &amp;#8230; ? jump on a call and ask them to walk you though&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;And after all that, you still end up &lt;span class="docutils literal"&gt;rm &lt;span class="pre"&gt;-rf&lt;/span&gt; build/&lt;/span&gt; at every other build because you don&amp;#8217;t trust the damn thing&amp;nbsp;&amp;#8230;&lt;/p&gt;
&lt;p&gt;And because it doesn&amp;#8217;t actually work when run in parallel, the compile times are agonising&amp;nbsp;&amp;#8230;&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="use-case-2-running-tasks"&gt;
&lt;h2&gt;Use case 2: Running&amp;nbsp;tasks&lt;/h2&gt;
&lt;p&gt;The concept of a dependency graph generalises well to many other problems, not just compiling&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;Using a makefile to express that the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;publish-docker&lt;/span&gt;&lt;/span&gt; rule depends on the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;build-docker&lt;/span&gt;&lt;/span&gt; rule seems natural. Sure you have to mark the rule &lt;span class="docutils literal"&gt;.&lt;span class="caps"&gt;PHONY&lt;/span&gt;&lt;/span&gt; to tell make that the outputs are not actual files &amp;#8230; but it&amp;nbsp;works.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;publish&lt;/span&gt;-&lt;span class="n"&gt;docker&lt;/span&gt;
&lt;span class="nf"&gt;publish-docker&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;-&lt;span class="n"&gt;docker&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt; &lt;/span&gt;my-image:latest

&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;-&lt;span class="n"&gt;docker&lt;/span&gt;
&lt;span class="nf"&gt;build-docker&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;my-image&lt;span class="w"&gt; &lt;/span&gt;docker/my-image
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Sure, it took you a couple of tries because you forgot to change your editor settings to use tabs for indentation instead of spaces, but it&amp;nbsp;works.&lt;/p&gt;
&lt;p&gt;But now you have a second image you want to build. You copy the rule, but feel icky. The whole point of using a makefile was to avoid this kind of&amp;nbsp;duplication.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;publish&lt;/span&gt;-&lt;span class="n"&gt;docker&lt;/span&gt;-&lt;span class="n"&gt;foo&lt;/span&gt;
&lt;span class="nf"&gt;publish-docker-foo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;-&lt;span class="n"&gt;docker&lt;/span&gt;-&lt;span class="n"&gt;foo&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt; &lt;/span&gt;my-foo-image:latest

&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;-&lt;span class="n"&gt;docker&lt;/span&gt;-&lt;span class="n"&gt;foo&lt;/span&gt;
&lt;span class="nf"&gt;build-docker-foo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;my-foo-image&lt;span class="w"&gt; &lt;/span&gt;docker/my-foo-image

&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;publish&lt;/span&gt;-&lt;span class="n"&gt;docker&lt;/span&gt;-&lt;span class="n"&gt;bar&lt;/span&gt;
&lt;span class="nf"&gt;publish-docker-bar&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;-&lt;span class="n"&gt;docker&lt;/span&gt;-&lt;span class="n"&gt;bar&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt; &lt;/span&gt;my-bar-image:latest

&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;-&lt;span class="n"&gt;docker&lt;/span&gt;-&lt;span class="n"&gt;bar&lt;/span&gt;
&lt;span class="nf"&gt;build-docker-bar&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;my-bar-image&lt;span class="w"&gt; &lt;/span&gt;docker/my-bar-image
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Instead of copying the rule, you&amp;#8217;d like to re-use and somehow pass the image name as a parameter &amp;#8230; and now you&amp;#8217;re stuck. A make wizard would spit out a magic command that generates the rules with more &lt;span class="docutils literal"&gt;make&lt;/span&gt; magic but how maintainable is&amp;nbsp;that?&lt;/p&gt;
&lt;p&gt;You&amp;#8217;ve hit the limits of make. It isn&amp;#8217;t made for this. You need an actual task runner. Something that doesn;t think in files, but in actual tasks, that might have arguments. There are many out there, but my personal go-to is &lt;a class="reference external" href="https://just.systems/"&gt;just&lt;/a&gt;.  The equivalent &lt;span class="docutils literal"&gt;justfile&lt;/span&gt;&amp;nbsp;is&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nf"&gt;build-docker-foo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;(&lt;span class="n"&gt;build&lt;/span&gt;-&lt;span class="n"&gt;docker&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;foo&lt;/span&gt;-&lt;span class="n"&gt;image&lt;/span&gt;&amp;quot;)
&lt;span class="nf"&gt;publish-docker-foo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;(&lt;span class="n"&gt;publish&lt;/span&gt;-&lt;span class="n"&gt;docker&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;foo&lt;/span&gt;-&lt;span class="n"&gt;image&lt;/span&gt;&amp;quot;)

&lt;span class="nf"&gt;build-docker-bar&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;(&lt;span class="n"&gt;build&lt;/span&gt;-&lt;span class="n"&gt;docker&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;bar&lt;/span&gt;-&lt;span class="n"&gt;image&lt;/span&gt;&amp;quot;)
&lt;span class="nf"&gt;publish-docker-bar&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;(&lt;span class="n"&gt;publish&lt;/span&gt;-&lt;span class="n"&gt;docker&lt;/span&gt; &amp;quot;&lt;span class="n"&gt;my&lt;/span&gt;-&lt;span class="n"&gt;bar&lt;/span&gt;-&lt;span class="n"&gt;image&lt;/span&gt;&amp;quot;)

&lt;span class="nf"&gt;publish-docker {{ IMAGE_NAME }}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;IMAGE_NAME&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;

&lt;span class="nf"&gt;build-docker IMAGE_NAME&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;image&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;IMAGE_NAME&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker/&lt;span class="o"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;IMAGE_NAME&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Isn&amp;#8217;t that better? Out-of-the-box we also get a list of rules (and a documentation comment for each one) with &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;#8212;list&lt;/span&gt;&lt;/span&gt;, the ability to group them in the output, the ability to import other &lt;span class="docutils literal"&gt;justfiles&lt;/span&gt; without recursive makefile hacks, the ability to execute the body of a rule as a script (no need to add a &lt;span class="docutils literal"&gt;\&lt;/span&gt; at the end of each line like in a makefile)&amp;nbsp;&amp;#8230;&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s &lt;em&gt;just&lt;/em&gt; a better tool. There are plenty of others out there that might fit your aesthetic better, just don&amp;#8217;t use &lt;span class="docutils literal"&gt;make&lt;/span&gt; for&amp;nbsp;this.&lt;/p&gt;
&lt;/section&gt;
</content><category term="misc"/><category term="make"/><category term="cmake"/><category term="just"/></entry><entry><title>What Makes a Job Worthwhile</title><link href="https://thibaud.space/2026/06/what-makes-a-job-worthwhile.html" rel="alternate"/><published>2026-06-26T00:00:00+02:00</published><updated>2026-06-26T20:05:07+02:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2026-06-26:/2026/06/what-makes-a-job-worthwhile.html</id><summary type="html">&lt;p&gt;We spend 8 hours a day working. For software engineers, currently in the enviable position to pick their workplace, instead of needing to accept whatever job offer that comes their way, the question is: &lt;em&gt;what makes a job worthwhile?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;What makes a job something worth dedicating half of our waking hours (and a larger portion of our productive hours) to?&lt;/p&gt;
</summary><content type="html">&lt;p&gt;We spend 8 hours a day working. For software engineers, currently in the enviable position to pick their workplace, instead of needing to accept whatever job offer that comes their way, the question is: &lt;em&gt;what makes a job&amp;nbsp;worthwhile?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;What makes a job something worth dedicating half of our waking hours (and a larger portion of our productive hours)&amp;nbsp;to?&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;The answer to the question is deeply&amp;nbsp;personal.&lt;/p&gt;
&lt;p&gt;For some, the comfort of a stable paycheck, and &lt;em&gt;the devil you know&lt;/em&gt;, is a a sufficient motivation to stay in place. For others, the potential for professional growth, or career growth are more&amp;nbsp;important.&lt;/p&gt;
&lt;p&gt;Having seen varying levels of dysfunction throughout the years, I think that, for me, the answer boils down&amp;nbsp;to&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;the potential for&amp;nbsp;growth;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the potential for&amp;nbsp;change;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;feeling valued as an&amp;nbsp;individual.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;section id="the-potential-for-growth"&gt;
&lt;h2&gt;The potential for&amp;nbsp;growth&lt;/h2&gt;
&lt;p&gt;Growth is an overloaded term, and what matters most will change from person-to-person. Is growth &lt;em&gt;personal growth&lt;/em&gt;, like improving your communication, or conflict management skills? Is growth &lt;em&gt;professional growth&lt;/em&gt; in the sense of getting better at your job, developing a good intuition for how to solve problems, or create maintainable systems? Is it about &lt;em&gt;professional growth&lt;/em&gt;: seeing your job title change from junior, to senior, to manager, &amp;#8230; Or is it a completely different&amp;nbsp;axis?&lt;/p&gt;
&lt;p&gt;The meaning of growth, and what actually matters to me, has changed oer time. As a junior, the most important thing was learning. Now, growth tends towards helping others&amp;nbsp;improve.&lt;/p&gt;
&lt;p&gt;In unstructured environments, it&amp;#8217;s been easy to pull a junior under my wing, and watch them grow. More structured environments make this harder: everybody has their own work, nicely siloed up, making it harder to understand other people&amp;#8217;s work well enough to&amp;nbsp;help.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-potential-for-change"&gt;
&lt;h2&gt;The Potential for&amp;nbsp;Change&lt;/h2&gt;
&lt;p&gt;All code is legacy code. The moment code gets merged into the main branch, it becomes legacy code that needs to be supported. The most important thing from that point on is whether or not the processes allow for incremental improvement on the existing&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;Refactoring and bug-fixes should not be work that is snuck in under the pretense of doing feature development. More broadly, as development practices evolve, the processes around the code should&amp;nbsp;change.&lt;/p&gt;
&lt;p&gt;For example, it seems unthinkable, in 2026, to start a new project without code-formatters, linters, continuous-integration, &amp;#8230; So why is still so common to see older projects that lack these basic building blocks of software&amp;nbsp;quality?&lt;/p&gt;
&lt;p&gt;Environments that refuse to make the time to modernise processes make simple maintenance harder, and make it easier to introduce errors. I never thought that I would see a project on which &lt;em&gt;&amp;#8220;Who broke the build?&amp;#8221;&lt;/em&gt; is still a common&amp;nbsp;question.&lt;/p&gt;
&lt;p&gt;Hearing a colleague, explain to me, with a straight face, that &lt;em&gt;&amp;#8220;the industry has moved away from code reviews because they add no value&amp;#8221;&lt;/em&gt;, was a&amp;nbsp;shock.&lt;/p&gt;
&lt;p&gt;Learning that the we cannot prohibit direct pushes to the master branch because an older colleague would not accept code reviews, was&amp;nbsp;unpleasant.&lt;/p&gt;
&lt;p&gt;If there is potential for change, the situation can be salvaged. But if current practices are stuck in stone, staying in place is equivalent to slowly de-skilling&amp;nbsp;myself.&lt;/p&gt;
&lt;p&gt;Without code reviews, I can only learn from my mistakes if I stay long enough to see their&amp;nbsp;effects.&lt;/p&gt;
&lt;p&gt;With code reviews, I can learn from the mistakes my colleagues have made in the&amp;nbsp;past.&lt;/p&gt;
&lt;p&gt;With linters, I can learn from my entire industry&amp;#8217;s&amp;nbsp;mistakes.&lt;/p&gt;
&lt;p&gt;Without any of those, I am stuck in place, slowly being losing ground to others, in more progressive&amp;nbsp;environments.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="feeling-valued-as-an-individual"&gt;
&lt;h2&gt;Feeling Valued as an&amp;nbsp;Individual&lt;/h2&gt;
&lt;p&gt;Companies will preach values, and forget them the moment the slideshow is over. I&amp;#8217;ve seen companies praise their efforts to create a friendly office environment, simultaneously not intervene when a senior engineer repeatedly threatened to throw me out of the window, in a &lt;em&gt;I&amp;#8217;m joking but deep down I really mean it&lt;/em&gt; kind of&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve seen companies explicitly say that they hire juniors and foreigners because they&amp;#8217;re inexpensive, and that they have no intention of keeping them longer than two&amp;nbsp;years.&lt;/p&gt;
&lt;p&gt;When I found a company that paid above market rate for talent, and offered reasonable perks to retain the staff they had, I was skeptical. In practice, they seemed to put effort into improving the workspace, improving the working environment, and motivating employees to&amp;nbsp;stay,&lt;/p&gt;
&lt;p&gt;When I reached out to &lt;span class="caps"&gt;HR&lt;/span&gt;, they said and did the right&amp;nbsp;things.&lt;/p&gt;
&lt;p&gt;When employees wrote an open-letter to management, they said and did the right&amp;nbsp;things.&lt;/p&gt;
&lt;p&gt;So maybe they really&amp;nbsp;cared.&lt;/p&gt;
&lt;p&gt;Yet here I was, scrolling Linkedin jobs, daring myself to click that &lt;em&gt;Easy Apply&lt;/em&gt; button&amp;nbsp;again.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-makes-it-all-worthwhile"&gt;
&lt;h2&gt;What makes it all&amp;nbsp;worthwhile?&lt;/h2&gt;
&lt;p&gt;Nostalgia is rose-tinted glasses, but some of the most fun I&amp;#8217;ve had has been in a workplace where the software team was a small island of sanity, surrounded by socially inept project managers, and where a significant part of the staff was somewhat incompetent. Some of the most soul-crushing work I&amp;#8217;ve had, was in a worksplace where the work was technically challenging, but the colleagues were so distant that, other than their names, I knew basically nothing about&amp;nbsp;them.&lt;/p&gt;
&lt;p&gt;Is there a workplace that ticks all three boxes? Is that actually a pre-requisite for me to enjoy my time there&amp;nbsp;long-term?&lt;/p&gt;
&lt;p&gt;I still don&amp;#8217;t know, and leads me back to the age-old question: is the grass greener somewhere&amp;nbsp;else?&lt;/p&gt;
&lt;/section&gt;
</content><category term="misc"/><category term="career"/><category term="happiness"/></entry><entry><title>Finding Meaning in Work</title><link href="https://thibaud.space/2026/06/finding-meaning-in-work.html" rel="alternate"/><published>2026-06-21T00:00:00+02:00</published><updated>2026-02-27T19:08:05+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2026-06-21:/2026/06/finding-meaning-in-work.html</id><summary type="html">&lt;p&gt;Given the amount of time and energy we expend in our workplaces, it makes sense to look for some kind of meaning in the work we do. We cannot all be &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Jonas_Salk"&gt;Jonas Salk&lt;/a&gt; (inventor of the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Polio_vaccine"&gt;Inactivated Polio Vaccine&lt;/a&gt;), or &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Frederick_Banting"&gt;Frederick Banting&lt;/a&gt; (first to isolate insulin, and origin of the &lt;a class="reference external" href="https://definingmomentscanada.ca/insulin100/history/early-patients/"&gt;Miracle at the Toronto General Hospital&lt;/a&gt;), but we can at least hope that our work is making the world a slightly better&amp;nbsp;place.&lt;/p&gt;
&lt;p&gt;However, if I were to judge the output of my career so far, I&amp;#8217;m not sure that any of it has contributed in a meaningful way …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Given the amount of time and energy we expend in our workplaces, it makes sense to look for some kind of meaning in the work we do. We cannot all be &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Jonas_Salk"&gt;Jonas Salk&lt;/a&gt; (inventor of the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Polio_vaccine"&gt;Inactivated Polio Vaccine&lt;/a&gt;), or &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Frederick_Banting"&gt;Frederick Banting&lt;/a&gt; (first to isolate insulin, and origin of the &lt;a class="reference external" href="https://definingmomentscanada.ca/insulin100/history/early-patients/"&gt;Miracle at the Toronto General Hospital&lt;/a&gt;), but we can at least hope that our work is making the world a slightly better&amp;nbsp;place.&lt;/p&gt;
&lt;p&gt;However, if I were to judge the output of my career so far, I&amp;#8217;m not sure that any of it has contributed in a meaningful way to improving&amp;nbsp;anything.&lt;/p&gt;
&lt;section id="working-in-academic-research"&gt;
&lt;h2&gt;Working in Academic&amp;nbsp;Research&lt;/h2&gt;
&lt;p&gt;My career started in an academic research lab, working on medical robots (think &lt;a class="reference external" href="https://www.intuitive.com/en-us/products-and-services/da-vinci"&gt;Da Vinci Surgical System&lt;/a&gt;). The project aimed to improve patient outcomes by adding virtual walls to the robot workspace to protect important anatomical structures. This required precise registration of pre-operative imagery with the operating&amp;nbsp;field.&lt;/p&gt;
&lt;p&gt;This registration was impossible. The pre-operative images were &lt;span class="caps"&gt;MRI&lt;/span&gt; scans, taken with the patient lying horizontally, but in the operating theatre, the patient was tilted about 10° downwards (with their head lower than their feet), and their abdominal cavity was inflated. This pushed all the organs away from their natural positions, and gave the surgeons more room to operate. It also made the pre-operative imagery useless since everything had&amp;nbsp;moved.&lt;/p&gt;
&lt;p&gt;After a year, we parted&amp;nbsp;ways.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="working-in-eu-projects"&gt;
&lt;h2&gt;Working in &lt;span class="caps"&gt;EU&lt;/span&gt;&amp;nbsp;Projects&lt;/h2&gt;
&lt;p&gt;My next job was for a company that made most of its money on European Union Funded projects, and had never made a single product in its entire lifetime. The organisation would bid on robotics projects, hack together a solution that worked just well enough to make a demo, and then move on to the next project. The strategy worked well, but the lab was full of abandoned prototypes of things that might have been viable&amp;nbsp;products.&lt;/p&gt;
&lt;p&gt;After four years of never finishing anything, I grew frustrated, and was not-so gently shown the&amp;nbsp;door.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="working-in-the-european-space-agency"&gt;
&lt;h2&gt;Working in the European Space&amp;nbsp;Agency&lt;/h2&gt;
&lt;p&gt;I wanted to work somewhere where there was an actual long-term goal, and I thought I had found it when I joined the European Space Agency. The lab had an illustrious history of ground breaking work, but by the time I joined, it was rudderless. The project that was driving the work was at best an engineering demo, not a serious research&amp;nbsp;goal.&lt;/p&gt;
&lt;p&gt;Within 18 months, I was&amp;nbsp;gone.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="working-in-industry"&gt;
&lt;h2&gt;Working in&amp;nbsp;Industry&lt;/h2&gt;
&lt;p&gt;I was done with research. I wanted real products, real&amp;nbsp;customers.&lt;/p&gt;
&lt;p&gt;I joined a small company making components for small satellites (less than 50 kg). I had high expectations, and was quickly disappointed. The company had survived on a steady diet of juniors, left without guidance, destined to repeat the mistakes of their predecessors. The code was cobbled together by electrical engineers, as a means to an end, mashing the keyboard until they thought it&amp;nbsp;worked.&lt;/p&gt;
&lt;p&gt;The firmware was brittle, assumed that everything would forever stay on the happy path, had no tests, or standards, or even a hint of good practices. Repositories were usually not up to date, and picking up a new project usually meant asking the head engineer to search through his virtual machines for his uncommitted changes. The existence of an &lt;em&gt;On Orbit Hardware-In-The-Loop Test&lt;/em&gt; frame in the communication protocol said a lot about the overall lack of quality. &lt;a class="brackets" href="#footnote-1" id="footnote-reference-1" role="doc-noteref"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;1&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To some extent, I am proud of the work I did there. Every day I would work on hardware that was on its way to space! I did my best to impose standards in the codebase, but I wonder how long they persisted after my&amp;nbsp;departure.&lt;/p&gt;
&lt;p&gt;But was the work actually&amp;nbsp;useful?&lt;/p&gt;
&lt;p&gt;The products we worked on were of questionable quality. One in-development product could not use the external oscillator for the microcontroller clock. The engineer who had designed it was long gone, and the project was too late to respin the board. As a result, the control loop could only use 0.5 % of the available control&amp;nbsp;action.&lt;/p&gt;
&lt;p&gt;The customer, who bankrolled the entire development effort, would have been better off killing the project, and buying off the shelf hardware from somebody&amp;nbsp;competent.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="working-in-defense"&gt;
&lt;h2&gt;Working in&amp;nbsp;Defense&lt;/h2&gt;
&lt;p&gt;After moving to a new country, I decided to change industry, and stepped into the world of defense. Learning little from my past experience, I had high&amp;nbsp;expectations.&lt;/p&gt;
&lt;p&gt;Aside from the habitual bureaucratic incompetence of large organisations, I was struck with confusion over the goals of the team. Had anybody asked me what the priorities for the next quarter, or the next year were, I would have been at a loss. Had anybody asked me what the most pressing issues with the hardware were, I would have been similarly&amp;nbsp;stumped.&lt;/p&gt;
&lt;p&gt;As I tried to onboard onto the project, trying to make a mental model of this undocumented monolith, I struggled to see how I would make an impact. I was hired with lofty goals: to become a bridge between their grumpy senior engineer and the junior programmers tasked with making his vision reality, but I was left chasing down the typos of a prolific, but clearly dyslexic&amp;nbsp;programmer.&lt;/p&gt;
&lt;p&gt;I entered the defense industry looking for purpose. I expected to be able to relate my work directly to current geopolitical priorities, but by the time the work landed on my plate, it had been stripped of all context, and purpose was lost with&amp;nbsp;it.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="what-if-the-journey-was-the-purpose"&gt;
&lt;h2&gt;What if the Journey was the&amp;nbsp;Purpose?&lt;/h2&gt;
&lt;p&gt;Looking back at the last 10 years of my life, I could not point to a single project I worked on that improved the world around it in any measurable&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;As I pondered this unpleasant thought, another one came into focus. I have always enjoyed teaching, and I had the opportunity to mentor quite a few interns and juniors along the&amp;nbsp;way.&lt;/p&gt;
&lt;p&gt;Even interns whom I thought I failed, seemed happy with what they learned working with me. Juniors I mentored grew into engineers I would hire again in a&amp;nbsp;heartbeat.&lt;/p&gt;
&lt;p&gt;If I failed to have a positive impact through my projects, maybe they&amp;nbsp;would.&lt;/p&gt;
&lt;aside class="footnote-list brackets"&gt;
&lt;aside class="footnote brackets" id="footnote-1" role="doc-footnote"&gt;
&lt;span class="label"&gt;&lt;span class="fn-bracket"&gt;[&lt;/span&gt;&lt;a role="doc-backlink" href="#footnote-reference-1"&gt;1&lt;/a&gt;&lt;span class="fn-bracket"&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;p&gt;&lt;em&gt;Hardware-In-The-Loop&lt;/em&gt; testing involves testing software on a system that is representative of the actual hardware. That might mean using the actual embedded microcontroller, but simulating sensor readings. Doing this kind of testing on-orbit meant that the company did not have representative hardware on ground that could be used for&amp;nbsp;testing.&lt;/p&gt;
&lt;/aside&gt;
&lt;/aside&gt;
&lt;/section&gt;
</content><category term="misc"/><category term="career"/></entry><entry><title>On Disappointment</title><link href="https://thibaud.space/2026/06/on-disappointment.html" rel="alternate"/><published>2026-06-19T00:00:00+02:00</published><updated>2026-06-20T21:36:14+02:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2026-06-19:/2026/06/on-disappointment.html</id><summary type="html">&lt;p&gt;Every new job comes with some amount of trepidation: how dysfunctional will this one be? Yet, I was excited about this one. I was moving back into aerospace: the land of exacting standards, and attention to detail. Having seen the chaos of &lt;em&gt;New Space&lt;/em&gt;, I wanted the calm competency of &lt;em&gt;Old Space&lt;/em&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Every new job comes with some amount of trepidation: how dysfunctional will this one be? Yet, I was excited about this one. I was moving back into aerospace: the land of exacting standards, and attention to detail. Having seen the chaos of &lt;em&gt;New Space&lt;/em&gt;, I wanted the calm competency of &lt;em&gt;Old Space&lt;/em&gt;.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;section id="first-contact"&gt;
&lt;h2&gt;First&amp;nbsp;Contact&lt;/h2&gt;
&lt;p&gt;The hiring process had been slow, but fair. The interviews had been challenging and interesting, so I was quite eager to&amp;nbsp;start.&lt;/p&gt;
&lt;p&gt;By the end of the first morning, I had a laptop (&lt;em&gt;Windows&lt;/em&gt; unfortunately), and login information for outlook and teams. A low bar, but one many companies fail to meet. Laptop in hand, I went to my new manager, and things took a turn for the&amp;nbsp;worse.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;You&amp;#8217;re going to read documentation for the first month&amp;#8221; he&amp;nbsp;said.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;span class="dquo"&gt;&amp;#8220;&lt;/span&gt;No, I&amp;#8217;m not!&amp;#8221; I responded,&amp;nbsp;reflexively.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;He assigned me documents to read, and I hit my first roadblock. I did not have access to the document management system, and could not access any of the assigned&amp;nbsp;documents.&lt;/p&gt;
&lt;p&gt;Things did not get better after that. After a week of reading increasingly repetitive and irrelevant documentation, my brain was melting: I could no longer focus on the handful of remaining pages. As my gaze passed over them, my eyes glazed up, and the content passed through me without ever encountering a conscious&amp;nbsp;thought.&lt;/p&gt;
&lt;p&gt;I begged for access to GitLab to at least take a look at the software for which I was reading the documentation. I was met by a certificate warning. I tried to clone the repository, and spent the rest of the afternoon looking for the root-certificate and installing&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;When I finally looked at the code, I was struck by cursed formatting styles, applied haphazardly, and without any&amp;nbsp;uniformity.&lt;/p&gt;
&lt;p&gt;This was not the high-quality codebase I hoped to&amp;nbsp;see.&lt;/p&gt;
&lt;p&gt;My first month crawled by: I was required to come to the office every day, and would sit in front of my computer, glumly, silently, only meeting colleagues at lunch, before sitting in traffic for an inordinate amount of time on my commute&amp;nbsp;home.&lt;/p&gt;
&lt;p&gt;I was&amp;nbsp;miserable.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="somehow-it-gets-worse"&gt;
&lt;h2&gt;Somehow, it gets&amp;nbsp;worse&lt;/h2&gt;
&lt;p&gt;With my first month up, I was assigned my first real work. Software&amp;nbsp;testing!&lt;/p&gt;
&lt;p&gt;Aerospace is big on paperwork, and one big chunk of paperwork is proving that you did enough testing. &lt;em&gt;Important&lt;/em&gt; systems require 100% unit-test coverage (or more accurately: require you to provide some kind of documentation showing that you have 100% test coverage). The quality of tests themselves is not examined, just the&amp;nbsp;number.&lt;/p&gt;
&lt;p&gt;Once those pass, a bevy of integration tests are required, showing that core requirements are&amp;nbsp;addressed.&lt;/p&gt;
&lt;p&gt;In a reasonable environment, this would be a handful of automated test runs. Run a handful of commands, get the logs, and attach them to the report. In a well-oiled system, the tests would be run automatically by the continuous-integration as part of a tag pipeline, and the certification work would be limited to downloading the automatically-generated test&amp;nbsp;report.&lt;/p&gt;
&lt;p&gt;This was not the case. Two weeks were budgeted for me to learn how to run the tests, followed by two weeks to actually run them. Another month of my life, commuting to work every day, for a job that had explicitly advertised 50%&amp;nbsp;home-working.&lt;/p&gt;
&lt;p&gt;This may have been the most difficult period of my entire professional career to that point. The steps to run the manual tests were often wrong, or incomplete. Worse, many of the tests had unclear pass/fail criteria, and in many cases there was no way to differentiate between a test that passed successfully, and a test that had been executed&amp;nbsp;incorrectly.&lt;/p&gt;
&lt;p&gt;The most egregious test though were the automated tests. Hours-long test suites, that failed to check any pre-conditions. Forget to run a service: half of the tests will fail with obscure error messages, for something that should have been detected in the first 5&amp;nbsp;seconds.&lt;/p&gt;
&lt;p&gt;I was going&amp;nbsp;insane.&lt;/p&gt;
&lt;p&gt;Somehow this agonising period also came to end, and I was promised my first real project. I was hopeful&amp;nbsp;again.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="all-hope-is-lost"&gt;
&lt;h2&gt;All hope is&amp;nbsp;lost&lt;/h2&gt;
&lt;p&gt;I cloned the repository, ran the build commands in the &lt;cite&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/cite&gt;, and was immediately stopped by a wall of&amp;nbsp;errors.&lt;/p&gt;
&lt;p&gt;The project had been entirely staffed by external contractors, working more-or-less independently, until that point. I was the first internal person to look at the code in a&amp;nbsp;year.&lt;/p&gt;
&lt;p&gt;I asked questions, found some answers, and eventually got the project building, but somehow, nobody was able to tell me, in any useful detail, what the short-term goals were, what was actually working, or not working, or even find references for the core algorithms that were required for the&amp;nbsp;project.&lt;/p&gt;
&lt;p&gt;As I looked deeper, I only grew more worried, the documentation was scarce, and most of what I could see was a half-baked, under-development, in-house framework, for &amp;#8230; message passing between&amp;nbsp;threads?&lt;/p&gt;
&lt;p&gt;Where was the actual meat of the&amp;nbsp;project?&lt;/p&gt;
&lt;p&gt;Where were the&amp;nbsp;tests?&lt;/p&gt;
&lt;p&gt;Where was the continuous&amp;nbsp;integration?&lt;/p&gt;
&lt;p&gt;Where was the&amp;nbsp;documentation?&lt;/p&gt;
&lt;p&gt;I grew more&amp;nbsp;concerned.&lt;/p&gt;
&lt;p&gt;I dug in some more, found some trivial race&amp;nbsp;conditions.&lt;/p&gt;
&lt;p&gt;I pushed deeper, and started to notice a total lack of error&amp;nbsp;handling.&lt;/p&gt;
&lt;p&gt;This was not what I expected. Instead of a carefully architected system, designed with the expertise and caution most people would associate with an aerospace company, what I had in front of me was an unnecessary framework, rapidly growing more warts as the core design problems became more&amp;nbsp;apparent.&lt;/p&gt;
&lt;p&gt;Here I was, on a project staffed by contractors, very much on the outside. Desperately trying to figure out what was going on, without even having access to the issue tracker, or an invite to the weekly progress&amp;nbsp;meeting.&lt;/p&gt;
&lt;p&gt;What the f&amp;#8230; was I supposed to do&amp;nbsp;now?&lt;/p&gt;
&lt;/section&gt;
&lt;section id="pushing-through"&gt;
&lt;h2&gt;Pushing&amp;nbsp;Through&lt;/h2&gt;
&lt;p&gt;Not one to give up easily, I rolled up my sleeves and tried to understand the writhing monster of a framework, growing every week, with undocumented, and untested code. At each refactor, the framework resisted, mutating under my fingers: a new push, attempting to fix another issue that should have been apparent from the start, haphazardly adding parameters to function I had&amp;nbsp;removed.&lt;/p&gt;
&lt;p&gt;Nobody wants to throw away a year of work, but faced with the realization that understanding undocumented code is harder than writing it from scratch, and absent any signs of an actual grand vision for the software architecture, maybe that would be the best path&amp;nbsp;forwards.&lt;/p&gt;
&lt;p&gt;I groaned silently. That would be a hard&amp;nbsp;sell.&lt;/p&gt;
&lt;/section&gt;
</content><category term="misc"/><category term="career"/></entry><entry><title>Ensure that Git Submodules are Initialized</title><link href="https://thibaud.space/2026/03/ensure-that-git-submodules-are-initialized.html" rel="alternate"/><published>2026-03-10T00:00:00+01:00</published><updated>2026-01-18T23:40:18+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2026-03-10:/2026/03/ensure-that-git-submodules-are-initialized.html</id><summary type="html">&lt;p&gt;You’ve checked out a new repository, try to build it, and are confronted with a wall of errors.&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;CI&lt;/span&gt; is green and your colleagues have assured you that all the dependencies are included in the repository. Why is the build failing?&lt;/p&gt;
&lt;p&gt;You scratch your head — why is your environment different?&lt;/p&gt;
&lt;p&gt;You look through the &lt;span class="docutils literal"&gt;CMakeLists.txt&lt;/span&gt;, do some grepping, but you can’t find those missing dependencies. Puzzled you look at the &lt;span class="docutils literal"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/span&gt;, and there you see it, you forgot to clone the submodules, …&lt;/p&gt;
&lt;p&gt;Facepalm …&lt;/p&gt;
&lt;p&gt;After making this mistake when cloning a new project too many times, I resolved to find a solution.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;You&amp;#8217;ve checked out a new repository, try to build it, and are confronted with a wall of&amp;nbsp;errors.&lt;/p&gt;
&lt;p&gt;The &lt;span class="caps"&gt;CI&lt;/span&gt; is green and your colleagues have assured you that all the dependencies are included in the repository. Why is the build&amp;nbsp;failing?&lt;/p&gt;
&lt;p&gt;You scratch your head &amp;#8212; why is your environment&amp;nbsp;different?&lt;/p&gt;
&lt;p&gt;You look through the &lt;span class="docutils literal"&gt;CMakeLists.txt&lt;/span&gt;, do some grepping, but you can&amp;#8217;t find those missing dependencies. Puzzled you look at the &lt;span class="docutils literal"&gt;&lt;span class="caps"&gt;README&lt;/span&gt;&lt;/span&gt;, and there you see it, you forgot to clone the submodules,&amp;nbsp;&amp;#8230;&lt;/p&gt;
&lt;p&gt;Facepalm&amp;nbsp;&amp;#8230;&lt;/p&gt;
&lt;p&gt;After making this mistake when cloning a new project too many times, I resolved to find a&amp;nbsp;solution.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;The idea is simple, check if the project has submodules, and make sure that each submodule is properly&amp;nbsp;initialised.&lt;/p&gt;
&lt;p&gt;In &lt;span class="docutils literal"&gt;git&lt;/span&gt; we can list submodules with &lt;span class="docutils literal"&gt;git submodule&lt;/span&gt;. Helpfully, the command tells us the state of each&amp;nbsp;submodule.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ git submodule status

 -xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx path/submodule_path (branch)
 +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx path/submodule_path (branch)
  xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx path/submodule_path (branch)
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A line starting with &lt;span class="docutils literal"&gt;-&lt;/span&gt; denotes a submodule that is not checked out yet, a line starting with &lt;span class="docutils literal"&gt;+&lt;/span&gt; denotes a submodule where the currently checked out commit does not match the hash of the commit from the &lt;span class="docutils literal"&gt;.gitmodules&lt;/span&gt; file (the submodule has changed, but the parent repository has not been&amp;nbsp;updated).&lt;/p&gt;
&lt;p&gt;Enter &lt;a class="reference external" href="https://gitlab.com/comkieffer/cmake-modules/-/blob/master/cmake/EnsureSubmodulesInitialised.cmake?ref_type=heads"&gt;EnsureSubmodulesInitialised&lt;/a&gt;. Just include it in your main &lt;span class="docutils literal"&gt;CMakeLists.txt&lt;/span&gt;, and if you ever forget to initialise a submodule, you&amp;#8217;ll get a nice helpful error&amp;nbsp;message.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;# Ensure that all submodules have been initialised.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# This is helpful in repositories that include dependencies as&lt;/span&gt;
&lt;span class="c"&gt;# submodules since it is easy to forget to initialise them on&lt;/span&gt;
&lt;span class="c"&gt;# clone, or to forget to initialise a new dependency after a pull.&lt;/span&gt;

&lt;span class="nb"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;ensure_submodules_initialised&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# Check if the submodule is initialized.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# If the repository was cloned without `--recursive`, the&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# submodule directory will be empty. If it has been&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# initialised, it will contain a `.git` directory. We use&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# it&amp;#39;s present as a marker to ensure that the submodule has&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# been initialised.&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;execute_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;COMMAND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;submodule&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;OUTPUT_VARIABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;_git_submodule_status&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;OUTPUT_STRIP_TRAILING_WHITESPACE&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;WORKING_DIRECTORY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROJECT_SOURCE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;COMMAND_ERROR_IS_FATAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ANY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# Replace newlines with `;` to make the result into a list&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;REGEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;REPLACE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;_git_submodule_status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;${_git_submodule_status}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;_has_uninitialised_submodule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;_submodule_status_line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_git_submodule_status&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;# examples of status lines&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;# -ac9dac21a68eacaa826c69a3b42d0aa69a3c68cf&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;#      path/submodule_path (branch)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;# +ac9dac21a68eacaa826c69a3b42d0aa69a3c68cf&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;#      path/submodule_path (branch)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;#  ac9dac21a68eacaa826c69a3b42d0aa69a3c68cf&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;#      path/submodule_path (branch)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;# A `-` in the first position indicates that the&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;# submodule is not initialised.&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;# Remove the leading space. It will only complicate&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;# parsing for us.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;STRIP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_submodule_status_line&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;_submodule_status_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;# Make the string into a list so that we get easier&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;# access to the different parts&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;REPLACE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;_submodule_status_line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_submodule_status_line&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;POP_FRONT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;_submodule_status_line&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;_submodule_hash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;_submodule_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;_submodule_branch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;FIND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_submodule_hash&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;_submodule_not_initialised&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;${_submodule_not_initialised}&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;EQUAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;STATUS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Submodule ${_submodule_path} is not initialised.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;_has_uninitialised_submodule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;endforeach&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;_has_uninitialised_submodule&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;FATAL_ERROR&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Some submodules were not initialised. \n&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Run &amp;#39;git submodule update --init --recursive&amp;#39; to &amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;initialise them.&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nb"&gt;endfunction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;aside class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Line&amp;nbsp;Wrapping&lt;/p&gt;
&lt;p&gt;This file has been reformatted for display on this blog. The file on &lt;a class="reference external" href="https://gitlab.com/comkieffer/cmake-modules/-/blob/master/cmake/EnsureSubmodulesInitialised.cmake?ref_type=heads"&gt;gitlab&lt;/a&gt; is properly&amp;nbsp;formatted.&lt;/p&gt;
&lt;/aside&gt;
</content><category term="misc"/><category term="git"/><category term="cmake"/></entry><entry><title>It Wasn’t Making me Happy - On Changing Jobs</title><link href="https://thibaud.space/2026/03/it-wasnt-making-me-happy-on-changing-jobs.html" rel="alternate"/><published>2026-03-06T00:00:00+01:00</published><updated>2026-06-20T20:44:19+02:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2026-03-06:/2026/03/it-wasnt-making-me-happy-on-changing-jobs.html</id><summary type="html">&lt;p&gt;I started a new job in January, and left in February. It wasn’t making me happy.&lt;/p&gt;
&lt;p&gt;It started like any other interview process: a video interview, followed, a few weeks later, by a site visit. They showed me their hardware, talked about the challenges they were facing, how they were trying to improve precision, trying to extend their system to do new things, reacting to market needs. I was impressed.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I started a new job in January, and left in February. It wasn&amp;#8217;t making me&amp;nbsp;happy.&lt;/p&gt;
&lt;p&gt;It started like any other interview process: a video interview, followed, a few weeks later, by a site visit. They showed me their hardware, talked about the challenges they were facing, how they were trying to improve precision, trying to extend their system to do new things, reacting to market needs. I was&amp;nbsp;impressed.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;It felt like they had an appreciation for&amp;nbsp;quality.&lt;/p&gt;
&lt;p&gt;It felt like they had hard engineering challenges to&amp;nbsp;solve.&lt;/p&gt;
&lt;p&gt;I felt like I might find my people&amp;nbsp;there.&lt;/p&gt;
&lt;p&gt;I was&amp;nbsp;optimistic.&lt;/p&gt;
&lt;section id="feeling-small"&gt;
&lt;h2&gt;Feeling&amp;nbsp;small&lt;/h2&gt;
&lt;p&gt;Most companies like to say that their biggest strength is their people. Few can actually articulate how they ensure that they keep the best people, but most at least try to give the impression that they try by having free coffee, or free snacks, or free &lt;em&gt;something&lt;/em&gt; (as long as it&amp;#8217;s&amp;nbsp;cheap).&lt;/p&gt;
&lt;p&gt;I had never worked anywhere that actively worked to give me the opposite&amp;nbsp;impression.&lt;/p&gt;
&lt;p&gt;The parking is a vacant lot, next to a gutted-out building, covered in shards of&amp;nbsp;glass.&lt;/p&gt;
&lt;p&gt;It took 4 days to get a laptop and an email&amp;nbsp;login.&lt;/p&gt;
&lt;p&gt;The laptop came with a pair of monitors. Two 1080p slabs, with pixels large enough to&amp;nbsp;count.&lt;/p&gt;
&lt;p&gt;The docking station was a &lt;span class="caps"&gt;USB&lt;/span&gt;-C hub from a no-name Chinese&amp;nbsp;brand.&lt;/p&gt;
&lt;p&gt;I asked for a &lt;span class="caps"&gt;QWERTY&lt;/span&gt; keyboard without a numpad. It was denied. The resulting email change went all the way up the org&amp;nbsp;chart.&lt;/p&gt;
&lt;p&gt;But most damning, the laptop didn&amp;#8217;t charge over &lt;span class="caps"&gt;USB&lt;/span&gt;-C.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="feeling-lost"&gt;
&lt;h2&gt;Feeling&amp;nbsp;lost&lt;/h2&gt;
&lt;p&gt;Starting in a new team means discovering a new codebase. Somebody points you at some out-of-date documentation full of wishful thinking and high hopes, and you&amp;#8217;re off&amp;nbsp;spelunking.&lt;/p&gt;
&lt;p&gt;Unfortunately, reading is all I could do. The hardware is large, and there&amp;#8217;s only a handful of units to use. There is no simulator for the system, so I could not actually run the code, and the tests are limited; large parts of the codebase are too tightly coupled to be easily unit&amp;nbsp;tested.&lt;/p&gt;
&lt;p&gt;If I can&amp;#8217;t run the code, I can at least study it. If only I could figure out what the relevant parts of it are. The class names are unhelpful, and the code itself is mostly lacking documentation. I stare at my open editor tabs: 5 of them are called &lt;span class="docutils literal"&gt;Controller&lt;/span&gt;. All of them do different&amp;nbsp;things.&lt;/p&gt;
&lt;p&gt;I ask for whatever documentation there is, and I am met with embarrassed&amp;nbsp;smiles.&lt;/p&gt;
&lt;p&gt;This codebase is hundreds of thousands of lines, takes a good 10 minutes to build on my beefy development machine, and I have no idea what it even&amp;nbsp;does.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m lost, I wasn&amp;#8217;t expecting a reverse-engineering&amp;nbsp;job.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="feeling-unimpressed"&gt;
&lt;h2&gt;Feeling&amp;nbsp;Unimpressed&lt;/h2&gt;
&lt;p&gt;My first task, and the only code I successfully merged in my time there, was easy enough, take a pattern from one file, and apply it to another. Add some unit tests. Watch the &lt;span class="caps"&gt;CI&lt;/span&gt; go&amp;nbsp;green.&lt;/p&gt;
&lt;p&gt;My second task was a simple change, it amounted to adding a single line to a&amp;nbsp;function.&lt;/p&gt;
&lt;p&gt;A good first issue on its&amp;nbsp;face.&lt;/p&gt;
&lt;p&gt;After some time finding my bearings, I add the required line, and try to add a unit&amp;nbsp;test.&lt;/p&gt;
&lt;p&gt;I&amp;nbsp;failed.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Oh yeah, that part of the code can&amp;#8217;t be unit tested. We&amp;#8217;ll just have to test on the actual&amp;nbsp;hardware.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We go to test the change on actual&amp;nbsp;hardware.&lt;/p&gt;
&lt;p&gt;Connect the special cable to the special connector, grab the ssh key to log on to the onboard computer, overwrite the binary on the target system, and &amp;#8230; &lt;cite&gt;&lt;span class="caps"&gt;SEGFAULT&lt;/span&gt;&lt;/cite&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Oh that happens every now and then. Look at the backtrace and just comment that line&amp;nbsp;out.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I look on bewildered and confused. This isn&amp;#8217;t the highly resilient codebase I was&amp;nbsp;expecting.&lt;/p&gt;
&lt;p&gt;I duly do as I&amp;#8217;m told, find the line, comment it out, rebuild, reflash, and &amp;#8230; the system still fails to&amp;nbsp;initialise.&lt;/p&gt;
&lt;p&gt;We look at the logs, scroll up, scroll down, scroll back up some more, ponder&amp;nbsp;&amp;#8230;&lt;/p&gt;
&lt;p&gt;After a brief moment, a&amp;nbsp;pronouncement:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The motors are acting up. There&amp;#8217;s nothing more we can do&amp;nbsp;today.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We make plans to return another day to test on another&amp;nbsp;system.&lt;/p&gt;
&lt;p&gt;A few weeks pass before we try again. This time, the code runs and the station boots. When we change a configuration file to see if the fix I had implemented works, another error comes up. Another subsystem is broken by the configuration change, my code never even&amp;nbsp;ran.&lt;/p&gt;
&lt;p&gt;I look on confused. Another&amp;nbsp;pronouncement:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The fix won&amp;#8217;t work.
This issue can only be handled at a much lower&amp;nbsp;level.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I drop my changes. The best I can do now is merge the cleanups I&amp;nbsp;did.&lt;/p&gt;
&lt;p&gt;I am lost, but, more than anything, I feel helpless. How am I supposed to become productive in this code&amp;nbsp;base?&lt;/p&gt;
&lt;p&gt;I do not understand what the code actually&amp;nbsp;does.&lt;/p&gt;
&lt;p&gt;I cannot even describe the high-level architecture of the&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;I cannot even list out the main components of the&amp;nbsp;code.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m totally lost. &lt;em&gt;Fuck&lt;/em&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="feeling-powerless"&gt;
&lt;h2&gt;Feeling&amp;nbsp;Powerless&lt;/h2&gt;
&lt;p&gt;If I can&amp;#8217;t land any changes to the actual software, I can at least try to improve the process. One of the quick wins would be to get people to actually write&amp;nbsp;documentation.&lt;/p&gt;
&lt;p&gt;In the past, I&amp;#8217;ve had good success with a merge request template that simply asks if the change requires new&amp;nbsp;documentation.&lt;/p&gt;
&lt;p&gt;When creating a new merge request, you now have to explain why the documentation does not need to be updated. Quite a few times, as I was writing the rationale, I realised what documentation actually needed to be&amp;nbsp;updated.&lt;/p&gt;
&lt;p&gt;The file, including comments, is about 20 lines&amp;nbsp;long.&lt;/p&gt;
&lt;p&gt;That should be a quick&amp;nbsp;win.&lt;/p&gt;
&lt;p&gt;I bring it up with the team lead, and he asks me to demo it to him. He&amp;#8217;s enthusiastic, and sends an email to the other team leaders asking them for their&amp;nbsp;opinions.&lt;/p&gt;
&lt;p&gt;Nobody&amp;nbsp;answers.&lt;/p&gt;
&lt;p&gt;It dies&amp;nbsp;there.&lt;/p&gt;
&lt;p&gt;The merge request that actually builds the documentation with doxygen, dead&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;The change to actually run the formatter in the &lt;span class="caps"&gt;CI&lt;/span&gt; pipeline. Also&amp;nbsp;dead.&lt;/p&gt;
&lt;p&gt;How&amp;nbsp;dispiriting.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="making-up-my-mind"&gt;
&lt;h2&gt;Making Up My&amp;nbsp;Mind&lt;/h2&gt;
&lt;p&gt;Career progression is about learning. Every job I&amp;#8217;ve had so far, even the ones that did not make me happy, had opportunities to learn more about the craft of software, the stack, or the&amp;nbsp;culture.&lt;/p&gt;
&lt;p&gt;I was hoping to see a quality-focused environment, obsessively eking out the last drips of performance from the control loops, but seeing the hardware judder around killed that&amp;nbsp;notion.&lt;/p&gt;
&lt;p&gt;I was expecting to find a culture of quality, striving to create a reliable system, but seeing the system crash on a regular basis absolved me of those&amp;nbsp;expectations.&lt;/p&gt;
&lt;p&gt;I left every day, more and more confused as to how I would ever become productive in the code base, and what I had to learn from&amp;nbsp;it.&lt;/p&gt;
&lt;p&gt;The decision was made, I would find something&amp;nbsp;else.&lt;/p&gt;
&lt;/section&gt;
</content><category term="misc"/><category term="career"/><category term="happiness"/></entry><entry><title>Model-View-ViewModel: Structuring UI Code</title><link href="https://thibaud.space/2026/03/model-view-viewmodel-structuring-ui-code.html" rel="alternate"/><published>2026-03-01T00:00:00+01:00</published><updated>2026-03-01T21:22:50+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2026-03-01:/2026/03/model-view-viewmodel-structuring-ui-code.html</id><summary type="html">&lt;div&gt;&lt;p&gt;Your user interface started off clean, but now the code is a rat’s nest of special cases, and no two parts of the &lt;span class="caps"&gt;UI&lt;/span&gt; can agree on what the data they should be displaying is?&lt;/p&gt;
&lt;p&gt;Maybe you need &lt;em&gt;&lt;span class="caps"&gt;MVVM&lt;/span&gt;&lt;/em&gt;, or &lt;em&gt;Model-View-Viewmodel&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;MVVM&lt;/span&gt; is an application design pattern often used in user‑interface programming. It provides a structure for organising applications that is particularly useful when multiple views need access to the same underlying data.&lt;/p&gt;
&lt;/div&gt;</summary><content type="html">&lt;div&gt;&lt;p&gt;Your user interface started off clean, but now the code is a rat’s nest of special cases, and no two parts of the &lt;span class="caps"&gt;UI&lt;/span&gt; can agree on what the data they should be displaying is?&lt;/p&gt;
&lt;p&gt;Maybe you need &lt;em&gt;&lt;span class="caps"&gt;MVVM&lt;/span&gt;&lt;/em&gt;, or &lt;em&gt;Model-View-Viewmodel&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;MVVM&lt;/span&gt; is an application design pattern often used in user‑interface programming. It provides a structure for organising applications that is particularly useful when multiple views need access to the same underlying data.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;&lt;span class="caps"&gt;MVVM&lt;/span&gt; defines the following components:&lt;/p&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;Model&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Provides access to the underlying data that should be displayed. This can be a direct reference to the data, or mediated by a data‑access‑layer (&lt;em&gt;e.g.&lt;/em&gt; an &lt;span class="caps"&gt;ORM&lt;/span&gt;).&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;View&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Responsible for actually drawing the contents onto the screen. It displays a representation of the &lt;em&gt;Model&lt;/em&gt;, receives user-input, and forwards the handling of these events to the &lt;em&gt;ViewModel&lt;/em&gt;.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;View Model&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;The &lt;em&gt;ViewModel&lt;/em&gt; provides an interface to access and mutate the &lt;em&gt;Model&lt;/em&gt;.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;section id="the-problem-multiple-views-bind-to-the-same-model"&gt;
&lt;h2&gt;The Problem - Multiple &lt;em&gt;Views&lt;/em&gt; bind to the same &lt;em&gt;Model&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;Imagine an application in which we need to display a &lt;em&gt;Person&lt;/em&gt; in multiple places.&lt;/p&gt;
&lt;p&gt;In a naive approach, where each &lt;em&gt;View&lt;/em&gt; directly references the &lt;em&gt;Model&lt;/em&gt;, there is no easy mechanism for a &lt;em&gt;View&lt;/em&gt; to notify other &lt;em&gt;Views&lt;/em&gt; of the same &lt;em&gt;Model&lt;/em&gt; that it changed.&lt;/p&gt;
&lt;div class="tab-container" data-tab-id="tabs-307599ac"&gt;&lt;div class="tab-buttons" role="tablist"&gt;&lt;button class="tab-button active" role="tab" aria-selected="true" aria-controls="tabs-307599ac-panel-0" id="tabs-307599ac-tab-0" data-tab-index="0"&gt;Textual (&lt;span class="caps"&gt;TUI&lt;/span&gt;)&lt;/button&gt;&lt;button class="tab-button" role="tab" aria-selected="false" aria-controls="tabs-307599ac-panel-1" id="tabs-307599ac-tab-1" data-tab-index="1"&gt;PySide6 (&lt;span class="caps"&gt;GUI&lt;/span&gt;)&lt;/button&gt;&lt;/div&gt;&lt;div class="tab-panel active" role="tabpanel" id="tabs-307599ac-panel-0" aria-labelledby="tabs-307599ac-tab-0"&gt;&lt;p&gt;Consider a &lt;abbr title="Terminal User Interface"&gt;&lt;span class="caps"&gt;TUI&lt;/span&gt;&lt;/abbr&gt; application (in this example, written with &lt;a class="reference external" href="https://textual.textualize.io/"&gt;textual&lt;/a&gt;), that displays the same &lt;span class="docutils literal"&gt;Person&lt;/span&gt; in two different &lt;span class="docutils literal"&gt;PersonView&lt;/span&gt; widgets.&lt;/p&gt;
&lt;img alt="Textual application using a model-view only approach, showing that the view is out-of-sync with the model." src="https://thibaud.space/static/2026-mvvm/textual_view_only.png" width="648" height="416"&gt;
&lt;p&gt;When pressing &lt;span class="docutils literal"&gt;i&lt;/span&gt; (a keybind registered on the &lt;span class="docutils literal"&gt;PersonView&lt;/span&gt;), the focused &lt;span class="docutils literal"&gt;PersonView&lt;/span&gt; is updated, but the other one is not. The data is out-of-sync: one view shows &lt;span class="docutils literal"&gt;Alice, age 34&lt;/span&gt;, and the other shows &lt;span class="docutils literal"&gt;Alice, age 30&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/usr/bin/env -S uv run&lt;/span&gt;
&lt;span class="c1"&gt;# /// script&lt;/span&gt;
&lt;span class="c1"&gt;# dependencies = ["textual"]&lt;/span&gt;
&lt;span class="c1"&gt;# ///&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;textual.app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ComposeResult&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;textual.widgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Footer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;textual.binding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Binding&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PersonView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;BINDINGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"increase_age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Increase Age"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;can_focus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, age &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Called when `i` is pressed, must manually update the view.&lt;/span&gt;
    &lt;span class="c1"&gt;# Does not have a mechanism to notify other views of the change.&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;action_increase_age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PersonApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;CSS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"""&lt;/span&gt;
&lt;span class="s2"&gt;    PersonView {&lt;/span&gt;
&lt;span class="s2"&gt;        text-align: center;&lt;/span&gt;
&lt;span class="s2"&gt;        content-align: center middle;&lt;/span&gt;
&lt;span class="s2"&gt;        height: 1fr;&lt;/span&gt;
&lt;span class="s2"&gt;    }&lt;/span&gt;
&lt;span class="s2"&gt;    """&lt;/span&gt;

    &lt;span class="c1"&gt;# Creates two ``PersonView`` over the same ``Person`` instance&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ComposeResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;PersonView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;PersonView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PersonApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div class="tab-panel" role="tabpanel" id="tabs-307599ac-panel-1" aria-labelledby="tabs-307599ac-tab-1"&gt;&lt;p&gt;A &lt;a class="reference external" href="https://pypi.org/project/PySide6/"&gt;PySide&lt;/a&gt; application that displays the same &lt;span class="docutils literal"&gt;Person&lt;/span&gt; object in two different &lt;span class="docutils literal"&gt;PersonView&lt;/span&gt; objects.&lt;/p&gt;
&lt;img alt="PySide6 application using a model-view only approach, showing that the view is out-of-sync with the model." src="https://thibaud.space/static/2026-mvvm/pyside_view_only.png" width="406" height="256"&gt;
&lt;p&gt;When the &lt;span class="docutils literal"&gt;increase_button&lt;/span&gt; on one &lt;span class="docutils literal"&gt;PersonView&lt;/span&gt; is pressed, the age on the underlying &lt;span class="docutils literal"&gt;Person&lt;/span&gt; object is increased, but there is no mechanism to notify the other view. The age shown there never changes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/usr/bin/env -S uv run&lt;/span&gt;
&lt;span class="c1"&gt;# /// script&lt;/span&gt;
&lt;span class="c1"&gt;# dependencies = ["PySide6"]&lt;/span&gt;
&lt;span class="c1"&gt;# ///&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;PySide6.QtWidgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;QApplication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;QMainWindow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;QWidget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;QVBoxLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;QHBoxLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;QLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;QPushButton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;PySide6.QtCore&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Qt&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PersonView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QWidget&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;

        &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QVBoxLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, age &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setAlignment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Qt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AlignmentFlag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AlignCenter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;increase_button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QPushButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Increase Age"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;increase_button&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clicked&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;increase_age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;increase_button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;increase_age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="c1"&gt;# Only this view updates - the other view won't!&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_display&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Must manually update the view&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, age &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PersonApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QMainWindow&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setWindowTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Person Model-View Example"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Alice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;central_widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QWidget&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;main_layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QVBoxLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Create two views bound to the same person&lt;/span&gt;
        &lt;span class="n"&gt;views_layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QHBoxLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PersonView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PersonView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;views_layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;views_layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;main_layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;views_layout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;central_widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main_layout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setCentralWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;central_widget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PersonApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;p&gt;If we increment the age, the widget is not automatically re-drawn. The developer must manually update the widgets each time the values change. This is error prone.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="the-solution-a-view-binding-to-a-viewmodel"&gt;
&lt;h2&gt;The Solution - A &lt;em&gt;View&lt;/em&gt; binding to a &lt;em&gt;ViewModel&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;A &lt;em&gt;ViewModel&lt;/em&gt; can provide an interface between the &lt;em&gt;Model&lt;/em&gt; and the &lt;em&gt;View&lt;/em&gt; that ensures that when the &lt;em&gt;Model&lt;/em&gt; is updated, the &lt;em&gt;View&lt;/em&gt; reflects this change.&lt;/p&gt;
&lt;p&gt;Textual and pyside provide slightly different mechanisms for this.&lt;/p&gt;
&lt;div class="tab-container" data-tab-id="tabs-46d835c6"&gt;&lt;div class="tab-buttons" role="tablist"&gt;&lt;button class="tab-button active" role="tab" aria-selected="true" aria-controls="tabs-46d835c6-panel-0" id="tabs-46d835c6-tab-0" data-tab-index="0"&gt;Textual&lt;/button&gt;&lt;button class="tab-button" role="tab" aria-selected="false" aria-controls="tabs-46d835c6-panel-1" id="tabs-46d835c6-tab-1" data-tab-index="1"&gt;PySide6&lt;/button&gt;&lt;/div&gt;&lt;div class="tab-panel active" role="tabpanel" id="tabs-46d835c6-panel-0" aria-labelledby="tabs-46d835c6-tab-0"&gt;&lt;p&gt;The &lt;em&gt;ViewModel&lt;/em&gt; binds to the &lt;em&gt;Model&lt;/em&gt; and exposes it’s attributes as &lt;em&gt;reactive&lt;/em&gt; attributes. Other widgets can be notified when the change with &lt;span class="docutils literal"&gt;self.watch(Widget, reactive_attribute, callback)&lt;/span&gt;.&lt;/p&gt;
&lt;img alt="Textual application with MVVM showing synchronized views" src="https://thibaud.space/static/2026-mvvm/textual_mvvm.png" width="648" height="416"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/usr/bin/env -S uv run&lt;/span&gt;
&lt;span class="c1"&gt;# /// script&lt;/span&gt;
&lt;span class="c1"&gt;# dependencies = ["textual"]&lt;/span&gt;
&lt;span class="c1"&gt;# ///&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;textual.app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ComposeResult&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;textual.reactive&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reactive&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;textual.widgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Footer&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;textual.widget&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Widget&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;textual.binding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Binding&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PersonViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;

    &lt;span class="c1"&gt;# Called automatically when self.age changes&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;watch_age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_age&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;increase_age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PersonDisplayView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Static&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;BINDINGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"i"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"increase_age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Increase Age"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;can_focus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PersonViewModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;view_model&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, age &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Called automatically when widget is added to the app&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;action_increase_age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;increase_age&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PersonMVVMApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;CSS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"""&lt;/span&gt;
&lt;span class="s2"&gt;    PersonDisplayView {&lt;/span&gt;
&lt;span class="s2"&gt;        text-align: center;&lt;/span&gt;
&lt;span class="s2"&gt;        content-align: center middle;&lt;/span&gt;
&lt;span class="s2"&gt;        height: 1fr;&lt;/span&gt;
&lt;span class="s2"&gt;    }&lt;/span&gt;
&lt;span class="s2"&gt;    """&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ComposeResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;view_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PersonViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;PersonDisplayView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;PersonDisplayView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PersonMVVMApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;div class="tab-panel" role="tabpanel" id="tabs-46d835c6-panel-1" aria-labelledby="tabs-46d835c6-tab-1"&gt;&lt;p&gt;PySide uses a more verbose mechanism, that functions in conceptually the same way. The &lt;em&gt;ViewModel&lt;/em&gt; binds to the &lt;em&gt;Model&lt;/em&gt; and exposes it’s attributes as &lt;span class="docutils literal"&gt;@Property&lt;/span&gt; (note the capital &lt;span class="docutils literal"&gt;P&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;When the &lt;em&gt;Property&lt;/em&gt; is changed, the associated signal is triggered (via the &lt;cite&gt;notify=&lt;/cite&gt; attribute of the &lt;em&gt;Property&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;Views&lt;/em&gt; subscribe to changes by &lt;em&gt;connecting&lt;/em&gt; to the &lt;em&gt;Signal&lt;/em&gt;. When the Signal is triggered, the callback passed in the &lt;span class="docutils literal"&gt;connect&lt;/span&gt; call is invoked.&lt;/p&gt;
&lt;img alt="PySide6 application with MVVM showing synchronized views" src="https://thibaud.space/static/2026-mvvm/pyside_mvvm.png" width="406" height="256"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/usr/bin/env -S uv run&lt;/span&gt;
&lt;span class="c1"&gt;# /// script&lt;/span&gt;
&lt;span class="c1"&gt;# dependencies = ["PySide6"]&lt;/span&gt;
&lt;span class="c1"&gt;# ///&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;PySide6.QtWidgets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;QApplication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;QMainWindow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;QWidget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;QVBoxLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;QHBoxLayout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;QLabel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;QPushButton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;PySide6.QtCore&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Qt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;QObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Property&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PersonViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QObject&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name_changed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;age_changed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;

    &lt;span class="nd"&gt;@Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;notify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name_changed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;

    &lt;span class="nd"&gt;@name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setter&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name_changed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;notify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;age_changed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;

    &lt;span class="nd"&gt;@age&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setter&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age_changed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;increase_age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PersonDisplayView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QWidget&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PersonViewModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;view_model&lt;/span&gt;

        &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QVBoxLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, age &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setAlignment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Qt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AlignmentFlag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AlignCenter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Add increase age button to each view&lt;/span&gt;
        &lt;span class="n"&gt;increase_button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QPushButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Increase Age (i)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;increase_button&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clicked&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;increase_age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;increase_button&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setShortcut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"i"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;increase_button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Connect to view model changes - views automatically update!&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name_changed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_update_display&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age_changed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_update_display&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_update_display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, age &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;view_model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PersonMVVMApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QMainWindow&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setWindowTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Person MVVM Example"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Bob"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;person_view_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PersonViewModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Create two views bound to the same view model&lt;/span&gt;
        &lt;span class="n"&gt;central_widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QWidget&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;main_layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QVBoxLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;views_layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QHBoxLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;views_layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PersonDisplayView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person_view_model&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;views_layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PersonDisplayView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person_view_model&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="n"&gt;main_layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;views_layout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;central_widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main_layout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setCentralWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;central_widget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;QApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PersonMVVMApp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Now, instead of incrementing the age directly on the &lt;em&gt;Model&lt;/em&gt; (which knows nothing about the user‑interface library we are using), we increment the age on the &lt;em&gt;ViewModel&lt;/em&gt;, which can inform other interested parties of the change.&lt;/p&gt;
&lt;p&gt;Our &lt;span class="caps"&gt;UI&lt;/span&gt; is now sync.&lt;/p&gt;
&lt;/section&gt;
&lt;/div&gt;</content><category term="misc"/><category term="textual-py"/><category term="pyside"/><category term="mvvm"/><category term="design-patterns"/></entry><entry><title>You Should: Make Meringue</title><link href="https://thibaud.space/2026/02/you-should-make-meringue.html" rel="alternate"/><published>2026-02-22T00:00:00+01:00</published><updated>2026-03-01T23:21:35+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2026-02-22:/2026/02/you-should-make-meringue.html</id><summary type="html">&lt;p&gt;You really should be making Meringue. It’s bloody easy, and will make you popular with whomever you share them with (friends, family, colleagues, …). I’d show you a picture, but I ate them all already.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;You really should be making Meringue. It&amp;#8217;s bloody easy, and will make you popular with whomever you share them with (friends, family, colleagues, &amp;#8230;). I&amp;#8217;d show you a picture, but I ate them all&amp;nbsp;already.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;You need, egg whites (as many as you have), and 50 g of white sugar per egg&amp;nbsp;white.&lt;/p&gt;
&lt;p&gt;Put the egg whites in a bowl, and mix them with an egg beater. Once the whites start to become firm, add half of the sugar. Once they are firm to add the rest of the sugar. The mix is firm enough when you can flip the bowl upside down without the mix falling&amp;nbsp;out.&lt;/p&gt;
&lt;aside class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Checking&amp;nbsp;Firmness&lt;/p&gt;
&lt;p&gt;To check the firmness of the slowly pull the whisk out of the meringue and observe how long it takes for the tips to fall back down. Once they start falling graciously instead of flopping, you&amp;#8217;re nearly&amp;nbsp;there.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Take a baking tray, and put a sheet of baking paper on it. If you have a piping bag, pipe out nice circular meringues, otherwise, use a spoon to drop dollops onto the&amp;nbsp;sheet.&lt;/p&gt;
&lt;p&gt;Stick the tray in the oven at 100 °C and leave them there for at least 3 hours. You don&amp;#8217;t need to pre-heat the oven, they are staying in there so long that it won&amp;#8217;t make a&amp;nbsp;difference.&lt;/p&gt;
&lt;p&gt;Meringue isn&amp;#8217;t really cooked. It&amp;#8217;s dehydrated. That&amp;#8217;s why it needs such a low temperature. You just need to sweat them until there isn&amp;#8217;t a drop of moisture left in&amp;nbsp;them.&lt;/p&gt;
&lt;aside class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Energy&amp;nbsp;Saving&lt;/p&gt;
&lt;p&gt;Allegedly, and I haven&amp;#8217;t tested this myself, if you have baked something in your oven, you can just stick your meringue in the still hot oven, and leave them there overnight. They should be done in the&amp;nbsp;morning.&lt;/p&gt;
&lt;/aside&gt;
&lt;section id="debugging-your-failures"&gt;
&lt;h2&gt;Debugging Your&amp;nbsp;Failures&lt;/h2&gt;
&lt;p&gt;If your meringue is sticky, you just didn&amp;#8217;t cook them long enough. They need to cook for a long time because you want to get absolutely all the moisture out of them. Just stick them into the oven again for an&amp;nbsp;hour.&lt;/p&gt;
&lt;p&gt;This even works to revive your meringue if it&amp;#8217;s been left out for a while and has absorbed humidity from the environment. Just stick them back in the oven to dehydrate them some&amp;nbsp;more.&lt;/p&gt;
&lt;p&gt;If your meringue is yellowish, the temperature was too high. Next time, lower the&amp;nbsp;temperature.&lt;/p&gt;
&lt;/section&gt;
</content><category term="misc"/><category term="cooking"/></entry><entry><title>You Should Add FeatureSummary to your CMake Projects</title><link href="https://thibaud.space/2026/01/you-should-add-featuresummary-to-your-cmake-projects.html" rel="alternate"/><published>2026-01-13T00:00:00+01:00</published><updated>2026-01-11T15:07:03+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2026-01-13:/2026/01/you-should-add-featuresummary-to-your-cmake-projects.html</id><summary type="html">&lt;p&gt;Here’s a common problem: your cmake build is failing with linker errors, and now your’re looking through your CMake logs and reading the compilation database to see what libraries were actually included.&lt;/p&gt;
&lt;p&gt;What if I told you that there was a better way? Enter &lt;a class="reference external" href="https://cmake.org/cmake/help/latest/module/FeatureSummary.html"&gt;Feature Summary&lt;/a&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Here&amp;#8217;s a common problem: your cmake build is failing with linker errors, and now your&amp;#8217;re looking through your CMake logs and reading the compilation database to see what libraries were actually&amp;nbsp;included.&lt;/p&gt;
&lt;p&gt;What if I told you that there was a better way? Enter &lt;a class="reference external" href="https://cmake.org/cmake/help/latest/module/FeatureSummary.html"&gt;Feature Summary&lt;/a&gt;.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;section id="basic-usage"&gt;
&lt;h2&gt;Basic&amp;nbsp;Usage&lt;/h2&gt;
&lt;p&gt;The &lt;span class="docutils literal"&gt;FeatureSummary&lt;/span&gt; module has two&amp;nbsp;parts&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;add_feature_info(&amp;lt;name&amp;gt;,&lt;/span&gt; &amp;lt;condition&amp;gt;, &amp;lt;description&amp;gt;)&lt;/span&gt; to add entries to the&amp;nbsp;summary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;feature_summary(&lt;span class="caps"&gt;WHAT&lt;/span&gt; &lt;span class="caps"&gt;ALL&lt;/span&gt;)&lt;/span&gt; to print the&amp;nbsp;summary.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, add &lt;span class="docutils literal"&gt;include(FeatureSummary)&lt;/span&gt; to make the commands available to downstream modules, then when declaring options, add them to the summary with &lt;span class="docutils literal"&gt;add_feature_info&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cmake_minimum_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;VERSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;3.28&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;FeatureSummaryExample&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;FeatureSummary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Will be reported in the feature summary without further intervention&lt;/span&gt;
&lt;span class="nb"&gt;find_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;Threads&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;REQUIRED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;find_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;Catch2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Create an option, and add it to feature summary&lt;/span&gt;
&lt;span class="nb"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;WERROR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Enable -Werror to turn warnings into errors&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;add_feature_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;Werror&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;WERROR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Enable -Werror to turn warnings into errors&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;RUN_CLANG_TIDY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Run clang-tidy as part of the build&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;add_feature_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Run Clang Tidy&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;RUN_CLANG_TIDY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Run clang tidy as part of the build&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Print the feature summary&lt;/span&gt;
&lt;span class="nb"&gt;feature_summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;WHAT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;aside class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Best&amp;nbsp;Practices&lt;/p&gt;
&lt;p&gt;You should wait until the very end of your CMake file to call &lt;span class="docutils literal"&gt;feature_summary&lt;/span&gt;. It will only print changes made to that point, so if you call too early, the result may not reflect the final state of the&amp;nbsp;build.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;When you run cmake, the options you set (or did not set) will be printed&amp;nbsp;out:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;-- The following features have been enabled:

 * Werror, Enable -Werror to turn warnings into errors

-- The following REQUIRED packages have been found:

 * Threads

-- The following features have been disabled:

 * Run Clang Tidy, Run clang tidy as part of the build

-- The following OPTIONAL packages have not been found:

 * Catch2 (required version &amp;gt;= 3)

-- Configuring done (0.4s)
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="making-it-less-verbose"&gt;
&lt;h2&gt;Making it Less&amp;nbsp;Verbose&lt;/h2&gt;
&lt;p&gt;Having to declare an &lt;span class="docutils literal"&gt;option&lt;/span&gt; and then calling &lt;span class="docutils literal"&gt;add_feature_info&lt;/span&gt; each time quickly becomes tedious. A quick script can improve this&amp;nbsp;significantly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;FeatureSummary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;add_feature_option&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;_variable_name&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;_option_description&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;_option_default&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;DEFINED&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;OFF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# option(&amp;lt;variable&amp;gt; &amp;quot;&amp;lt;help_text&amp;gt;&amp;quot; [value])&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_variable_name&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_option_description&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_option_default&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;# add_feature_info(&amp;lt;name&amp;gt; &amp;lt;enabled&amp;gt; &amp;lt;description&amp;gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;_option_value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;${_variable_name&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;add_feature_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;${PROJECT_NAME}::${_variable_name}&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_option_value&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_option_description&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;endfunction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This function declares the option, and adds it to the feature summary in a single pass, saving us quite some typing (and ensuring that all options are always in the feature&amp;nbsp;summary).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;cmake_minimum_required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;VERSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;3.28&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;FeatureSummaryExample&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;FeatureSummary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;AddFeatureOption&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Will be reported in the feature summary without further intervention&lt;/span&gt;
&lt;span class="nb"&gt;find_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;Catch2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;QUIET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Create an option, and add it to feature summary&lt;/span&gt;
&lt;span class="nb"&gt;add_feature_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;WERROR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Enable -Werror to turn warnings into errors&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;False&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;add_feature_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;RUN_CLANG_TIDY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Run clang-tidy as part of the build&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;False&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Print the feature summary&lt;/span&gt;
&lt;span class="nb"&gt;feature_summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;WHAT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ALL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
</content><category term="misc"/><category term="cmake"/></entry><entry><title>A Justfile for Docker</title><link href="https://thibaud.space/2026/01/a-justfile-for-docker.html" rel="alternate"/><published>2026-01-10T00:00:00+01:00</published><updated>2026-01-10T23:56:42+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2026-01-10:/2026/01/a-justfile-for-docker.html</id><summary type="html">&lt;p&gt;Each new project I start ends up needing a dedicated container for &lt;span class="caps"&gt;CI&lt;/span&gt;. Some then accumulate additional containers for other &lt;span class="caps"&gt;CI&lt;/span&gt; jobs.&lt;/p&gt;
&lt;p&gt;Remembering the commands to build and publish these containers usually became a game of grepping the shell history in search of the correct commands. After doing this too many times, I now have a &lt;span class="docutils literal"&gt;Justfile&lt;/span&gt; to store these commands.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Each new project I start ends up needing a dedicated container for &lt;span class="caps"&gt;CI&lt;/span&gt;. Some then accumulate additional containers for other &lt;span class="caps"&gt;CI&lt;/span&gt;&amp;nbsp;jobs.&lt;/p&gt;
&lt;p&gt;Remembering the commands to build and publish these containers usually became a game of grepping the shell history in search of the correct commands. After doing this too many times, I now have a &lt;span class="docutils literal"&gt;Justfile&lt;/span&gt; to store these&amp;nbsp;commands.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;aside class="admonition note"&gt;
&lt;p class="admonition-title"&gt;&lt;a class="reference external" href="https://just.systems/"&gt;Just&lt;/a&gt; is &lt;em&gt;just&lt;/em&gt; a command&amp;nbsp;runner.&lt;/p&gt;
&lt;p&gt;Think make without the cruft, but also without the dependency tracking. It is just a way of storing sequences of commands (called recipes) that might be useful later. Out-of-the-box it provides a useful &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;#8212;list&lt;/span&gt;&lt;/span&gt; command, recipe dependencies, and an easy way to parametrize&amp;nbsp;recipes.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;I usually create a &lt;span class="docutils literal"&gt;docker/&lt;/span&gt; directory in the root of the repository, and put my dockerfiles&amp;nbsp;there.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&amp;lt;repo-root&amp;gt;
    - justfile
    - docker/
        - docker.justfile
        - pre-commit.dockerfile
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;span class="docutils literal"&gt;docker.justfile&lt;/span&gt; is included in the main &lt;span class="docutils literal"&gt;Justfile&lt;/span&gt; with &lt;span class="docutils literal"&gt;import &amp;#8220;docker/docker.justfile&amp;#8221;&lt;/span&gt;. This helps to keep the length of main &lt;span class="docutils literal"&gt;Justfile&lt;/span&gt;&amp;nbsp;manageable.&lt;/p&gt;
&lt;p&gt;Without further ado, here is the &lt;span class="docutils literal"&gt;Justfile&lt;/span&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;#
# Top-Level Rules
#

[group(&amp;quot;Build Docker Images&amp;quot;)]
build-docker: \
    (build-docker-pre-commit)

[group(&amp;quot;Publish Docker Images&amp;quot;)]
publish-docker: \
    (publish-docker-pre-commit)

#
# Image-specific rules
#

[group(&amp;quot;Build Docker Images&amp;quot;)]
build-docker-pre-commit: (_build-docker &amp;quot;pre-commit&amp;quot;)

[group(&amp;quot;Publish Docker Images&amp;quot;)]
publish-docker-pre-commit: \
    build-docker-pre-commit \
    (_publish-docker &amp;quot;pre-commit&amp;quot;)

#
# Helper rules
#

GITLAB_REGISTRY_URL:= &amp;quot;registry.gitlab.com&amp;quot;
GITLAB_REGISTRY_NS := GITLAB_REGISTRY_URL + &amp;quot;/com_kieffer/comkieffer.com&amp;quot;

GIT_HASH := `git describe --always --dirty`

_build-docker image_name:
    @echo &amp;quot;Tagging {{ image_name }} with {{ GIT_HASH }}&amp;quot;
    docker image build                                                 \
        --file docker/{{ image_name }}.dockerfile       \
        --tag {{ GITLAB_REGISTRY_NS }}/{{ image_name }}:{{ GIT_HASH }} \
        docker/
    docker image tag                                                   \
        {{ GITLAB_REGISTRY_NS }}/{{ image_name }}:{{ GIT_HASH }}       \
        {{ GITLAB_REGISTRY_NS }}/{{ image_name }}:latest

_publish-docker image_name:
    docker image push {{ GITLAB_REGISTRY_NS }}/{{ image_name }}:{{ GIT_HASH }}
    docker image push {{ GITLAB_REGISTRY_NS }}/{{ image_name }}:latest
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Starting from the bottom, we have a pair of rules: &lt;span class="docutils literal"&gt;_build_docker&lt;/span&gt;, and &lt;span class="docutils literal"&gt;_publish_docker&lt;/span&gt;. Since they start with an underscore, they are &lt;em&gt;private&lt;/em&gt; rules, and they are not shown in the &lt;span class="docutils literal"&gt;just &lt;span class="pre"&gt;&amp;#8212;list&lt;/span&gt;&lt;/span&gt;&amp;nbsp;output.&lt;/p&gt;
&lt;p&gt;The &lt;span class="docutils literal"&gt;_build_docker&lt;/span&gt; rule, takes a single argument: the name of the image (without the &lt;span class="docutils literal"&gt;.dockerfile&lt;/span&gt; extension). This name will become the container name. Once the image is built, it receives the &lt;span class="docutils literal"&gt;latest&lt;/span&gt; tag, as well was being tagged with the current commit short hash. This makes it easier to find out when an image was built and what it actually&amp;nbsp;contains.&lt;/p&gt;
&lt;p&gt;Make sure to edit the &lt;span class="docutils literal"&gt;GITLAB_REGISTRY_NS&lt;/span&gt; to give the images the correct&amp;nbsp;name.&lt;/p&gt;
&lt;p&gt;The &lt;span class="docutils literal"&gt;_publish_docker&lt;/span&gt; command just publishes the images to the&amp;nbsp;registry.&lt;/p&gt;
&lt;p&gt;These rules give us an easy way of building containers. For each container we created, we add a &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;build-docker-&amp;lt;container-name&amp;gt;&lt;/span&gt;&lt;/span&gt; and &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;publish-docker-&amp;lt;container-name&amp;gt;&lt;/span&gt;&lt;/span&gt; to build and publish the&amp;nbsp;container.&lt;/p&gt;
&lt;p&gt;Finally, we add these rules to the main &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;build-docker&lt;/span&gt;&lt;/span&gt; and &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;publish-docker&lt;/span&gt;&lt;/span&gt;&amp;nbsp;rules.&lt;/p&gt;
&lt;p&gt;In general, running &lt;span class="docutils literal"&gt;just &lt;span class="pre"&gt;publish-docker&lt;/span&gt;&lt;/span&gt; is enough to build and publish all the&amp;nbsp;images.&lt;/p&gt;
</content><category term="misc"/><category term="docker"/><category term="just"/></entry><entry><title>From Regolith to Cosmic DE</title><link href="https://thibaud.space/2026/01/from-regolith-to-cosmic-de.html" rel="alternate"/><published>2026-01-10T00:00:00+01:00</published><updated>2026-01-10T23:56:42+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2026-01-10:/2026/01/from-regolith-to-cosmic-de.html</id><summary type="html">&lt;p&gt;I recently bought a new laptop, and since I have some time on my hands, I decided to explore the landscape of tiling window managers again. I’ve been using &lt;a class="reference external" href="https://regolith-desktop.com/"&gt;Regolith Linux&lt;/a&gt; for the past 5 years, and I’ve developed a fair bit of muscle memory for it’s shortcuts.&lt;/p&gt;
&lt;p&gt;Regolith combines &lt;a class="reference external" href="https://www.gnome.org/"&gt;Gnome&lt;/a&gt; with &lt;a class="reference external" href="https://i3wm.org/"&gt;i3&lt;/a&gt; (or &lt;a class="reference external" href="https://swaywm.org/"&gt;Sway&lt;/a&gt; on Wayland). You get a good tiling window manager, but also the collection of tools that make a desktop environment (lock screen, monitor configuration, desktop backgrounds, notifications, …). Instead of needing to assemble all of these components yourself, Regolith gives you a working system out-of-the-box.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I recently bought a new laptop, and since I have some time on my hands, I decided to explore the landscape of tiling window managers again. I&amp;#8217;ve been using &lt;a class="reference external" href="https://regolith-desktop.com/"&gt;Regolith Linux&lt;/a&gt; for the past 5 years, and I&amp;#8217;ve developed a fair bit of muscle memory for it&amp;#8217;s&amp;nbsp;shortcuts.&lt;/p&gt;
&lt;p&gt;Regolith combines &lt;a class="reference external" href="https://www.gnome.org/"&gt;Gnome&lt;/a&gt; with &lt;a class="reference external" href="https://i3wm.org/"&gt;i3&lt;/a&gt; (or &lt;a class="reference external" href="https://swaywm.org/"&gt;Sway&lt;/a&gt; on Wayland). You get a good tiling window manager, but also the collection of tools that make a desktop environment (lock screen, monitor configuration, desktop backgrounds, notifications, &amp;#8230;). Instead of needing to assemble all of these components yourself, Regolith gives you a working system&amp;nbsp;out-of-the-box.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;I don&amp;#8217;t have many gripes about it. It generally works, and the defaults are fine. Still, I wanted to take stock of the existing landscape and see if anything better has popped&amp;nbsp;up.&lt;/p&gt;
&lt;p&gt;In 2025, the options seem to&amp;nbsp;be&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://system76.com/cosmic"&gt;Cosmic &lt;span class="caps"&gt;DE&lt;/span&gt;&lt;/a&gt; is a tiling window manager made by &lt;a class="reference external" href="https://system76.com/"&gt;System 76&lt;/a&gt;. It started as an extension for Gnome Shell, and eventually became a stand-alone desktop&amp;nbsp;environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://omakub.org/"&gt;Omarchy&lt;/a&gt; an Arch-based Linux distribution using&amp;nbsp;hyprland.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://kde.org/"&gt;&lt;span class="caps"&gt;KDE&lt;/span&gt;&lt;/a&gt; +&amp;nbsp;i3/sway&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whilst Omarchy seems interesting, the entire desktop configuration is text-based, and I enjoy not needing google in order to accomplish a task like positioning my external&amp;nbsp;monitor.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;KDE&lt;/span&gt; and i3/sway seem to play nicely together, but I just never enjoyed &lt;span class="caps"&gt;KDE&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;This leaves us with&amp;nbsp;Cosmic.&lt;/p&gt;
&lt;p&gt;After using it for a few weeks, I can say that it is quite a polished product. Some things are still missing (_e.g._ setting up the fingerprint reader) but it is quite&amp;nbsp;usable.&lt;/p&gt;
&lt;p&gt;At this stage, a couple of things are missing to make me stay with&amp;nbsp;it.&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p&gt;Easy workspace&amp;nbsp;manipulation&lt;/p&gt;
&lt;p&gt;In my Regolith setup, I often move entire workspaces from one output to the another with a single shortcut. I have not been able to get this to work in Cosmic. For example, I often have &lt;a class="reference external" href="https://www.sublimemerge.com/"&gt;Sublime Merge&lt;/a&gt; (my &lt;span class="caps"&gt;GUI&lt;/span&gt; client of choice), and a terminal open on a secondary monitor. When I need to commit/fixup.rebase/&amp;#8230; I often move the entire workspace to my main monitor. With Cosmic, I would need to move each window&amp;nbsp;individually.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unique workspace numbers across&amp;nbsp;outputs&lt;/p&gt;
&lt;p&gt;In i3, workspace numbers are unique across all outputs. Using &lt;span class="docutils literal"&gt;Meta&lt;/span&gt; + &lt;span class="docutils literal"&gt;1&lt;/span&gt; moves focus to that workspace, whatever output it is on. In Cosmic, this takes me to the first workspace on the current&amp;nbsp;output.&lt;/p&gt;
&lt;p&gt;This is especially important to me, because I tend to keep applications on specific workspaces. For example, workspace 1 is my main browser window, workspace 2 is chat and email, workspace 4 is my git tool, workspace 5 is my main &lt;span class="caps"&gt;IDE&lt;/span&gt; window, &amp;#8230; I often move the workspaces from one output to another depending on the task at hand. My git tool is usually on a secondary display, but if I need to dive into the repository history looking for a change, I will bring it onto the main display. Whatever display it is on though, it will always be workspace&amp;nbsp;4.&lt;/p&gt;
&lt;p&gt;Not having this makes jumping to a specific application slightly&amp;nbsp;slower.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All-in-all, I enjoy working with Cosmic, but the paradigm is just different enough from i3 to feel foreign. I might revisit it in a year or two, but in the short term, I&amp;#8217;ll be re-installing&amp;nbsp;Regolith.&lt;/p&gt;
</content><category term="misc"/><category term="linux"/><category term="i3-wm"/><category term="sway-wm"/><category term="cosmic-de"/></entry><entry><title>Automatically compiling PySide UI and resource files</title><link href="https://thibaud.space/2025/08/automatically-compiling-pyside-ui-and-resource-files.html" rel="alternate"/><published>2025-08-31T00:00:00+02:00</published><updated>2026-01-10T23:56:42+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2025-08-31:/2025/08/automatically-compiling-pyside-ui-and-resource-files.html</id><summary type="html">&lt;p&gt;The common workflow when working with Pyside is to build the &lt;span class="caps"&gt;UI&lt;/span&gt; with &lt;a class="reference external" href="https://doc.qt.io/qtforpython-6/tools/pyside-designer.html"&gt;PySide Designer&lt;/a&gt; which outputs an xml file (&lt;span class="docutils literal"&gt;.ui`&lt;/span&gt;). This file is then fed into &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;pyside6-uic`&lt;/span&gt;&lt;/span&gt; to generate the python code that actually the window.&lt;/p&gt;
&lt;p&gt;Resource files work in a similar manner. A resource file describes resources (&lt;em&gt;e.g.&lt;/em&gt; fonts and images) that should be included into the application. These are described in an xml file (&lt;span class="docutils literal"&gt;.qrc`&lt;/span&gt;) and compiled into Python by &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;pyside6-rcc&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Keeping the xml and compiled files synced by hand is tedious. A common option is to use a makefile to automatically re-generate the python files when the xml files are updated.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;The common workflow when working with Pyside is to build the &lt;span class="caps"&gt;UI&lt;/span&gt; with &lt;a class="reference external" href="https://doc.qt.io/qtforpython-6/tools/pyside-designer.html"&gt;PySide Designer&lt;/a&gt; which outputs an xml file (&lt;span class="docutils literal"&gt;.ui`&lt;/span&gt;). This file is then fed into &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;pyside6-uic`&lt;/span&gt;&lt;/span&gt; to generate the python code that actually the&amp;nbsp;window.&lt;/p&gt;
&lt;p&gt;Resource files work in a similar manner. A resource file describes resources (&lt;em&gt;e.g.&lt;/em&gt; fonts and images) that should be included into the application. These are described in an xml file (&lt;span class="docutils literal"&gt;.qrc`&lt;/span&gt;) and compiled into Python by &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;pyside6-rcc&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Keeping the xml and compiled files synced by hand is tedious. A common option is to use a makefile to automatically re-generate the python files when the xml files are&amp;nbsp;updated.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;nav class="contents" id="contents" role="doc-toc"&gt;
&lt;p class="topic-title"&gt;&lt;a class="reference internal" href="#top"&gt;Contents&lt;/a&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#a-simple-makefile" id="toc-entry-1"&gt;A Simple&amp;nbsp;makefile&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#a-simple-justfile" id="toc-entry-2"&gt;A Simple&amp;nbsp;Justfile&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#make-or-just" id="toc-entry-3"&gt;Make or Just&amp;nbsp;?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#bonus-content-use-fzf-to-select-the-file-to-edit" id="toc-entry-4"&gt;Bonus Content: Use fzf to select the file to&amp;nbsp;edit&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;section id="a-simple-makefile"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-1" role="doc-backlink"&gt;A Simple&amp;nbsp;makefile&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here is a simple &lt;em&gt;Makefile&lt;/em&gt; to compile your &lt;span class="docutils literal"&gt;.ui&lt;/span&gt; and &lt;span class="docutils literal"&gt;.qrc&lt;/span&gt;&amp;nbsp;files&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;# Find all .ui files and generate corresponding .py target names&lt;/span&gt;
&lt;span class="nv"&gt;UI_FILES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;src/&lt;span class="w"&gt; &lt;/span&gt;-name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*.ui&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;UI_PY_FILES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;patsubst&lt;span class="w"&gt; &lt;/span&gt;%.ui,&lt;span class="k"&gt;$(&lt;/span&gt;dir&lt;span class="w"&gt; &lt;/span&gt;%&lt;span class="k"&gt;)&lt;/span&gt;ui_&lt;span class="k"&gt;$(&lt;/span&gt;notdir&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;basename&lt;span class="w"&gt; &lt;/span&gt;%&lt;span class="k"&gt;))&lt;/span&gt;.py,&lt;span class="k"&gt;$(&lt;/span&gt;UI_FILES&lt;span class="k"&gt;))&lt;/span&gt;

&lt;span class="c"&gt;# QRC file and target&lt;/span&gt;
&lt;span class="nv"&gt;QRC_FILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rcc/resources.qrc
&lt;span class="nv"&gt;QRC_PY_FILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;src/quinspect/resources.py

&lt;span class="c"&gt;# Default target&lt;/span&gt;
&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;-&lt;span class="n"&gt;ui&lt;/span&gt;-&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="n"&gt;compile&lt;/span&gt;-&lt;span class="n"&gt;qrc&lt;/span&gt;-&lt;span class="n"&gt;files&lt;/span&gt;

&lt;span class="c"&gt;# Convenience targets&lt;/span&gt;
&lt;span class="nf"&gt;compile-ui-files&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;UI_PY_FILES&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;compile-qrc-files&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;QRC_PY_FILE&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Rule to compile .ui files to .py files&lt;/span&gt;
&lt;span class="nf"&gt;src/%/ui_%.py&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;/%/%.&lt;span class="n"&gt;ui&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;@echo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Compiling &lt;/span&gt;$&lt;span class="s2"&gt;&amp;lt; ...&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;.venv/bin/pyside6-uic&lt;span class="w"&gt; &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;$&amp;lt;

&lt;span class="c"&gt;# Rule to compile .qrc file to .py file&lt;/span&gt;
&lt;span class="nf"&gt;$(QRC_PY_FILE)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;QRC_FILE&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;@echo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Compiling &lt;/span&gt;$&lt;span class="s2"&gt;&amp;lt; ...&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;.venv/bin/pyside6-rcc&lt;span class="w"&gt; &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;$&amp;lt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="a-simple-justfile"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-2" role="doc-backlink"&gt;A Simple&amp;nbsp;Justfile&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Makefiles may be powerful, but the syntax can be hard to grasp (and remember). For simple tasks, I like to use &lt;cite&gt;just &amp;lt;https://just.systems/&amp;gt;`_&lt;/cite&gt;, a simple task&amp;nbsp;runner.&lt;/p&gt;
&lt;aside class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;&lt;span class="docutils literal"&gt;just&lt;/span&gt; is a handy way to save and run project-specific commands. It bills itself as a &lt;em&gt;command runner&lt;/em&gt;, not a &lt;em&gt;build system&lt;/em&gt;. The main difference is that instead of tracking dependencies between files, it tracks dependencies between&amp;nbsp;tasks.&lt;/p&gt;
&lt;p&gt;It assumes that the tasks themselves are smart enough to avoid doing redundant&amp;nbsp;work.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;The big difference between the &lt;em&gt;Justfile&lt;/em&gt; and the &lt;em&gt;Makefile&lt;/em&gt; is that &lt;span class="docutils literal"&gt;make`&lt;/span&gt; will only run a rule if the input is newer than the output. With &lt;span class="docutils literal"&gt;just`&lt;/span&gt;, rules are always run, so we need to do this&amp;nbsp;ourselves.&lt;/p&gt;
&lt;p&gt;Here is our &lt;span class="docutils literal"&gt;Justfile&lt;/span&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;just&lt;span class="w"&gt; &lt;/span&gt;--list&lt;span class="w"&gt; &lt;/span&gt;--justfile&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;justfile&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;

&lt;span class="c"&gt;# Compile .ui files if the generated code is out-of-date or missing&lt;/span&gt;
&lt;span class="err"&gt;[group(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Generators&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;)]&lt;/span&gt;
&lt;span class="nf"&gt;compile-ui-files&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="c"&gt;    #!/bin/bash&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ui_file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;src/&lt;span class="w"&gt; &lt;/span&gt;-name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*.ui&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;ui_file_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;basename&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ui_file&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;py_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;dirname&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ui_file&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/ui_&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ui_file_name&lt;/span&gt;&lt;span class="p"&gt;/%.ui/.py&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="cp"&gt;        if [[ ! -f $py_file || $ui_file -nt $py_file ]]; then&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Compiling &amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$ui_file&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; ...&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;.venv/bin/pyside6-uic&lt;span class="w"&gt; &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$py_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ui_file&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;


&lt;span class="c"&gt;# Compile .rc files if the generated code is out-of-date or missing&lt;/span&gt;
&lt;span class="err"&gt;[group(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Generators&amp;quot;&lt;/span&gt;&lt;span class="err"&gt;)]&lt;/span&gt;
&lt;span class="nf"&gt;compile-qrc-files&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="c"&gt;    #!/bin/bash&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;rc_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rcc/resources.qrc
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;py_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;src/quinspect/resources.py

&lt;span class="cp"&gt;    if [[ ! -f $py_file || $rc_file -nt $py_file ]]; then&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Compiling &amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$rc_file&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; ...&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;.venv/bin/pyside6-uic&lt;span class="w"&gt; &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$py_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rc_file
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This &lt;span class="docutils literal"&gt;Justfile&lt;/span&gt; defines three rules: &lt;span class="docutils literal"&gt;default&lt;/span&gt;, &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;compile-ui-files&lt;/span&gt;&lt;/span&gt;, and &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;compile-qrc-files&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;span class="docutils literal"&gt;default&lt;/span&gt; rule is called when &lt;span class="docutils literal"&gt;just&lt;/span&gt; is invoked without arguments, and simply lists the supported&amp;nbsp;commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt; $ &lt;/span&gt;just
&lt;span class="go"&gt;just --list --justfile justfile&lt;/span&gt;

&lt;span class="go"&gt;Available recipes:&lt;/span&gt;
&lt;span class="go"&gt;    default&lt;/span&gt;

&lt;span class="go"&gt;    [Generators]&lt;/span&gt;
&lt;span class="go"&gt;    compile-qrc-files # Compile .rc files if the generated code is out-of-date or missing&lt;/span&gt;
&lt;span class="go"&gt;    compile-ui-files  # Compile .ui files if the generated code is out-of-date or missing&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;compile-ui-files&lt;/span&gt;&lt;/span&gt; looks for &lt;span class="docutils literal"&gt;.ui&lt;/span&gt; files, inside the &lt;span class="docutils literal"&gt;src/&lt;/span&gt; directory, and runs a command for each&amp;nbsp;one.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ui_file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;src/&lt;span class="w"&gt; &lt;/span&gt;-name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*.ui&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# contents of for loop elided&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For each files we find, we want to generate the compiled Python version in the same directory, just prefixed with &lt;span class="docutils literal"&gt;ui_&lt;/span&gt; to make it&amp;nbsp;identifiable.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Get the file-name (stripping out the leading directories)&lt;/span&gt;
&lt;span class="nv"&gt;ui_file_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;basename&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ui_file&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create the file python file in the same directory (dirname $ui_file), add the `ui_` prefix to the file name, and the replace the ``.ui`` extension with `.py`&lt;/span&gt;
&lt;span class="nv"&gt;py_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;dirname&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ui_file&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/ui_&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ui_file_name&lt;/span&gt;&lt;span class="p"&gt;/%.ui/.py&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, if the python file does not exists, or the &lt;span class="docutils literal"&gt;.ui&lt;/span&gt; file has been modified more recently, re-generate the python&amp;nbsp;file.&lt;/p&gt;
&lt;p&gt;To check if the file exists, we use &lt;span class="docutils literal"&gt;! &lt;span class="pre"&gt;-f&lt;/span&gt; $py_file&lt;/span&gt;, where &lt;span class="docutils literal"&gt;!` negates the &lt;span class="pre"&gt;&amp;#8220;-f`&lt;/span&gt;&lt;/span&gt; condition (argument is a&amp;nbsp;file).&lt;/p&gt;
&lt;p&gt;To compare the modification times, we use the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;-nt&lt;/span&gt;&lt;/span&gt; (or &lt;em&gt;newer than&lt;/em&gt;) operator to &lt;span class="docutils literal"&gt;[[&lt;/span&gt;. If &lt;span class="docutils literal"&gt;$ui_file&lt;/span&gt; is newer than the &lt;span class="docutils literal"&gt;$py_file&lt;/span&gt;, them we run the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;pyside6-uic&lt;/span&gt;&lt;/span&gt;&amp;nbsp;compiler.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$py_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ui_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-nt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$py_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Compiling &amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$ui_file&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; ...&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;.venv/bin/pyside6-uic&lt;span class="w"&gt; &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$py_file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ui_file&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;compile-qrc-files`&lt;/span&gt;&lt;/span&gt; rule follows the same logic. However, since we only have one &lt;span class="docutils literal"&gt;.qrc`&lt;/span&gt; file, and the location of the output file is less straightforward, we hard-code it into the&amp;nbsp;rule.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="make-or-just"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-3" role="doc-backlink"&gt;Make or Just&amp;nbsp;?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So, should you use a &lt;em&gt;Makefile&lt;/em&gt; or a &lt;em&gt;Justfile&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;If you already have a &lt;em&gt;Makefile&lt;/em&gt;, by all means use a &lt;span class="docutils literal"&gt;make&lt;/span&gt; to update the &lt;span class="docutils literal"&gt;.ui&lt;/span&gt; files. You can see how much easier it&amp;nbsp;is.&lt;/p&gt;
&lt;p&gt;If you already have a &lt;em&gt;Justfile&lt;/em&gt;, you might want to keep adding to it. I personally think that the syntax of the &lt;em&gt;Justfile&lt;/em&gt; is easier to read, even if the rules end up being more verbose. However, in the age of coding assistants that can generate makefiles and explain them, this might not be as large an issue as it used to&amp;nbsp;be.&lt;/p&gt;
&lt;p&gt;Of course, you can also use your justfile to run your makefile. That might be the best of both&amp;nbsp;worlds.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="bonus-content-use-fzf-to-select-the-file-to-edit"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#toc-entry-4" role="doc-backlink"&gt;Bonus Content: Use fzf to select the file to&amp;nbsp;edit&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since writing the first draft of this article, I have extended the &lt;span class="docutils literal"&gt;justfile&lt;/span&gt; with the following&amp;nbsp;rule.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;# Select a .ui file to edit with PySide6 Designer&lt;/span&gt;
&lt;span class="nf"&gt;edit-ui&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;fdfind&lt;span class="w"&gt; &lt;/span&gt;--extension&lt;span class="w"&gt; &lt;/span&gt;ui&lt;span class="w"&gt; &lt;/span&gt;--type&lt;span class="w"&gt; &lt;/span&gt;f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fzf&lt;span class="w"&gt; &lt;/span&gt;--layout&lt;span class="o"&gt;=&lt;/span&gt;reverse&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;--preview&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;batcat --style=numbers --color=always {}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;--select-1&lt;span class="w"&gt; &lt;/span&gt;--exit-0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;xargs&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;poetry&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;pyside6-designer

&lt;span class="w"&gt;    &lt;/span&gt;just&lt;span class="w"&gt; &lt;/span&gt;--justfile&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;justfile&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;compile-ui-files
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When run, it shows a list of ui files (with a preview of the file contents), and opens the selected file in PySide Designer. When the designer is closed, the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;compile-ui-files&lt;/span&gt;&lt;/span&gt; command is run to recompile the changed&amp;nbsp;files.&lt;/p&gt;
&lt;p&gt;A small change, but a definite&amp;nbsp;time-saver.&lt;/p&gt;
&lt;/section&gt;
</content><category term="misc"/><category term="pyside"/><category term="just"/></entry><entry><title>A better cv::waitKey function</title><link href="https://thibaud.space/2017/12/a-better-cvwaitkey-function.html" rel="alternate"/><published>2017-12-08T00:00:00+01:00</published><updated>2026-01-10T23:56:42+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-12-08:/2017/12/a-better-cvwaitkey-function.html</id><summary type="html">&lt;p&gt;The deceptively name OpenCV &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;cv::waitKey&lt;/span&gt;&lt;/span&gt; in OpenCV is the centre of any use of the &lt;span class="docutils literal"&gt;highgui&lt;/span&gt; module. This module makes it easy to create simple user interfaces and the &lt;span class="docutils literal"&gt;waitKey&lt;/span&gt; function, in addition to waiting for keyboard events, is responsible for updating the message pump for all the windows.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;The deceptively name OpenCV &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;cv::waitKey&lt;/span&gt;&lt;/span&gt; in OpenCV is the centre of any use of the &lt;span class="docutils literal"&gt;highgui&lt;/span&gt; module. This module makes it easy to create simple user interfaces and the &lt;span class="docutils literal"&gt;waitKey&lt;/span&gt; function, in addition to waiting for keyboard events, is responsible for updating the message pump for all the&amp;nbsp;windows.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;The prototype of the function&amp;nbsp;is:&lt;/p&gt;
&lt;pre class="code C++ literal-block"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;cv::waitKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mills&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;span class="docutils literal"&gt;millis&lt;/span&gt; is the amount of time to wait. If &lt;span class="docutils literal"&gt;millis&lt;/span&gt; is &lt;span class="docutils literal"&gt;0&lt;/span&gt; the functions waits until a key is pressed. Otherwise, if &lt;span class="docutils literal"&gt;millis&lt;/span&gt; is greater than &lt;span class="docutils literal"&gt;0&lt;/span&gt; the function will return the keycode for the pressed key (a positive integer) if a key was pressed within the allotted time or a negative&amp;nbsp;number.&lt;/p&gt;
&lt;p&gt;The usual use of &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;cv::waitKey&lt;/span&gt;&lt;/span&gt; is to read keypresses in order to drive the user interface. This is usually done&amp;nbsp;as:&lt;/p&gt;
&lt;pre class="code C++ literal-block"&gt;&lt;code&gt;&lt;span class="k"&gt;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;keycode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;waitKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keycode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;'q'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Escape
&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is not very easy to use. Finding the right keycode usually involves looking up old pieces of code I wrote to get the mapping from the specific key we are interested in to the keycode. Let&amp;#8217;s design a better&amp;nbsp;interface.&lt;/p&gt;
&lt;p&gt;In other libraries with keyboard handling components such as &lt;a class="reference external" href="https://www.sfml-dev.org/"&gt;&lt;span class="caps"&gt;SFML&lt;/span&gt;&lt;/a&gt; keys are represented by a &lt;span class="docutils literal"&gt;KeyCode&lt;/span&gt; struct. Let&amp;#8217;s start with&amp;nbsp;that:&lt;/p&gt;
&lt;pre class="code C++ literal-block"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;CVKeyCode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Backspace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Tab&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Enter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="cm"&gt;/* snip, snip */&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="n"&gt;Num0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Num1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;49&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Num2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Num3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="cm"&gt;/* snip, snip */&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;97&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="cm"&gt;/* snip, snip */&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also need to wrap the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;cv::waitKey&lt;/span&gt;&lt;/span&gt; function to make it return a &lt;span class="docutils literal"&gt;KeyCode&lt;/span&gt;:&lt;/p&gt;
&lt;pre class="code C++ literal-block"&gt;&lt;code&gt;&lt;span class="kr"&gt;inline&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;KeyCode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;waitKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;KeyCode&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;waitKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we used a strongly typed &lt;span class="docutils literal"&gt;enum class&lt;/span&gt; instead of a raw &lt;span class="docutils literal"&gt;enum&lt;/span&gt; we cannot convert the &lt;span class="docutils literal"&gt;int&lt;/span&gt; implicitly to a &lt;span class="docutils literal"&gt;CVKeyCode&lt;/span&gt;, we need to cast explicitly with &lt;span class="docutils literal"&gt;static_cast&amp;lt;CVKeyCOde&amp;gt;( &amp;#8230; )&lt;/span&gt;. Using a strongly typed enumeration in this fashion makes it easier to identify the possible values that the enum can take which allows the compiler to warn use when we use them&amp;nbsp;wrong.&lt;/p&gt;
</content><category term="misc"/><category term="C++"/><category term="OpenCV"/></entry><entry><title>Quick-Tip: Check that your documentation is up-to-date with clang</title><link href="https://thibaud.space/2017/09/quick-tip-check-that-your-documentation-is-up-to-date-with-clang.html" rel="alternate"/><published>2017-09-13T00:00:00+02:00</published><updated>2026-01-10T23:58:03+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-09-13:/2017/09/quick-tip-check-that-your-documentation-is-up-to-date-with-clang.html</id><summary type="html">&lt;p&gt;It is too easy to forget to update documentation when refactoring code. I often change the parameters of a function and forget to update the doc doc comment which lives on, wrong and misguiding. In c++-land &lt;span class="docutils literal"&gt;doxygen&lt;/span&gt; is the standard documentation generator and it can be configured to warn when a parameter of a function is not documented. This helps but documenting every single parameter of every single function is hard, especially when developing new features which leads to a high false positive&amp;nbsp;rate.&lt;/p&gt;
&lt;p&gt;A finer grained approach that only warns if the documentation comment and the function disagree …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It is too easy to forget to update documentation when refactoring code. I often change the parameters of a function and forget to update the doc doc comment which lives on, wrong and misguiding. In c++-land &lt;span class="docutils literal"&gt;doxygen&lt;/span&gt; is the standard documentation generator and it can be configured to warn when a parameter of a function is not documented. This helps but documenting every single parameter of every single function is hard, especially when developing new features which leads to a high false positive&amp;nbsp;rate.&lt;/p&gt;
&lt;p&gt;A finer grained approach that only warns if the documentation comment and the function disagree would be&amp;nbsp;valuable.&lt;/p&gt;
&lt;p&gt;Our knight in shining armour today is the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;-Wdocumentation&lt;/span&gt;&lt;/span&gt; flag for &lt;span class="docutils literal"&gt;clang&lt;/span&gt;. With this flag enabled the compiler will parse the documentation comments and warn if the method signature does not match the documentation every time you rebuild your project, not simply when you rebuild your&amp;nbsp;documentation.&lt;/p&gt;
&lt;p&gt;To enable this feature you can add the following snippet to your &lt;span class="docutils literal"&gt;CMakeLists.txt&lt;/span&gt;&amp;nbsp;file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;CMAKE_CXX_COMPILER_ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;MATCHES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Clang&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;add_definitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-Wdocumentation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This snippet checks that the compiler is &lt;span class="docutils literal"&gt;clang&lt;/span&gt; and enables the&amp;nbsp;flag.&lt;/p&gt;
&lt;p&gt;Alternatively to add the flag to a single target you can use &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;target_compile_options($&lt;span class="caps"&gt;TARGET&lt;/span&gt;&lt;/span&gt; &lt;span class="caps"&gt;PRIVATE&lt;/span&gt; &lt;span class="pre"&gt;-Wdocumentation)&lt;/span&gt;&lt;/span&gt;. This will append the specified flags to the compiler invocation when building the specified&amp;nbsp;target.&lt;/p&gt;
</content><category term="misc"/><category term="C++"/><category term="clang"/><category term="doxygen"/><category term="cmake"/><category term="cpp"/></entry><entry><title>Fixing Nodelet does not exist errors in ROS</title><link href="https://thibaud.space/2017/09/fixing-nodelet-does-not-exist-errors-in-ros.html" rel="alternate"/><published>2017-09-06T00:00:00+02:00</published><updated>2026-01-10T23:58:03+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-09-06:/2017/09/fixing-nodelet-does-not-exist-errors-in-ros.html</id><summary type="html">&lt;p&gt;This error is caused by the fact that &lt;span class="caps"&gt;ROS&lt;/span&gt; does not know about the nodelet. To be sure we can check what nodelets &lt;span class="caps"&gt;ROS&lt;/span&gt; knows about by&amp;nbsp;running:&lt;/p&gt;
&lt;pre class="literal-block"&gt;$ rosrun nodelet declared_nodelets&lt;/pre&gt;
&lt;p&gt;If the nodelet you are trying to run is not listed in the output then we have confirmed that &lt;span class="caps"&gt;ROS&lt;/span&gt; does not know about it. If you are trying to run a nodelet from another package this usually means that the package itself is not in your &lt;span class="docutils literal"&gt;ROS_PACKAGE_PATH&lt;/span&gt;. This can happen if, for example, the package is not correctly&amp;nbsp;installed.&lt;/p&gt;
&lt;p&gt;If you are seeing this error for a nodelet …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This error is caused by the fact that &lt;span class="caps"&gt;ROS&lt;/span&gt; does not know about the nodelet. To be sure we can check what nodelets &lt;span class="caps"&gt;ROS&lt;/span&gt; knows about by&amp;nbsp;running:&lt;/p&gt;
&lt;pre class="literal-block"&gt;$ rosrun nodelet declared_nodelets&lt;/pre&gt;
&lt;p&gt;If the nodelet you are trying to run is not listed in the output then we have confirmed that &lt;span class="caps"&gt;ROS&lt;/span&gt; does not know about it. If you are trying to run a nodelet from another package this usually means that the package itself is not in your &lt;span class="docutils literal"&gt;ROS_PACKAGE_PATH&lt;/span&gt;. This can happen if, for example, the package is not correctly&amp;nbsp;installed.&lt;/p&gt;
&lt;p&gt;If you are seeing this error for a nodelet that you wrote some possible reasons&amp;nbsp;are:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;In the package declaring your nodelets the &lt;span class="docutils literal"&gt;exports&lt;/span&gt; block does not link to your &lt;span class="docutils literal"&gt;nodelet_plugin.xml&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your &lt;span class="docutils literal"&gt;package.xml&lt;/span&gt; does not list &lt;span class="docutils literal"&gt;nodelet&lt;/span&gt; as a&amp;nbsp;dependency&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the nodelet is listed in the &lt;span class="docutils literal"&gt;declared_nodelets&lt;/span&gt; but still cannot be launched some possible reason&amp;nbsp;are:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;The class name specified in the &lt;span class="docutils literal"&gt;nodelet_plugins.xml&lt;/span&gt; file does not match the name of the class implementing the nodelet. This usually happens to me when I forget which namespace the nodelet is&amp;nbsp;in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You forgot to add the &lt;cite&gt;&amp;#8216;PLUGINLIB_EXPORT_CLASS&lt;/cite&gt; macro to the &lt;span class="docutils literal"&gt;.cpp&lt;/span&gt;&amp;nbsp;file&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Even dumber, you forgot to add the implementation of the nodelet to your &lt;span class="docutils literal"&gt;CMakeLists.txt&lt;/span&gt;&amp;nbsp;file&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</content><category term="misc"/><category term="ros"/></entry><entry><title>A CMake Include Rule for IDEs</title><link href="https://thibaud.space/2017/08/a-cmake-include-rule-for-ides.html" rel="alternate"/><published>2017-08-04T00:00:00+02:00</published><updated>2026-01-10T23:56:42+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-08-04:/2017/08/a-cmake-include-rule-for-ides.html</id><summary type="html">&lt;p&gt;When creating a &lt;cite&gt;CMakeLists.txt&lt;/cite&gt; file we would like to only list the &lt;cite&gt;.cpp&lt;/cite&gt; files in the &lt;cite&gt;add_executable&lt;/cite&gt; and &lt;cite&gt;add_library&lt;/cite&gt; commands. Whilst this is sufficient to compile the code (and should be enough for any &lt;span class="caps"&gt;IDE&lt;/span&gt;) most &lt;span class="caps"&gt;IDE&lt;/span&gt;&amp;#8217;s today will exhibit limited functionality unless the header files are also included in the &lt;cite&gt;add_executable&lt;/cite&gt; and &lt;cite&gt;add_library&lt;/cite&gt; commands.&lt;/p&gt;
&lt;p&gt;A simple workaround that preserves the best of both worlds is to create a dummy rule that includes all the headers but doesn&amp;#8217;t actually do anything. better yet, we can make the rule locate all the headers with a simple &lt;cite&gt;&lt;span class="caps"&gt;GLOB&lt;/span&gt;&lt;/cite&gt; expression …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When creating a &lt;cite&gt;CMakeLists.txt&lt;/cite&gt; file we would like to only list the &lt;cite&gt;.cpp&lt;/cite&gt; files in the &lt;cite&gt;add_executable&lt;/cite&gt; and &lt;cite&gt;add_library&lt;/cite&gt; commands. Whilst this is sufficient to compile the code (and should be enough for any &lt;span class="caps"&gt;IDE&lt;/span&gt;) most &lt;span class="caps"&gt;IDE&lt;/span&gt;&amp;#8217;s today will exhibit limited functionality unless the header files are also included in the &lt;cite&gt;add_executable&lt;/cite&gt; and &lt;cite&gt;add_library&lt;/cite&gt; commands.&lt;/p&gt;
&lt;p&gt;A simple workaround that preserves the best of both worlds is to create a dummy rule that includes all the headers but doesn&amp;#8217;t actually do anything. better yet, we can make the rule locate all the headers with a simple &lt;cite&gt;&lt;span class="caps"&gt;GLOB&lt;/span&gt;&lt;/cite&gt; expression:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;# Dummy target for IDE&amp;#39;s&lt;/span&gt;
&lt;span class="nb"&gt;FILE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;GLOB_RECURSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;all_headers_for_ides&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_SOURCE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/**/*.hpp&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_SOURCE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/**/*.h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;ADD_CUSTOM_TARGET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;ide_includes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;SOURCES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;all_headers_for_ides&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</content><category term="misc"/><category term="cmake"/></entry><entry><title>Enable locate on systems with and encrypted home directory</title><link href="https://thibaud.space/2017/08/enable-locate-on-systems-with-and-encrypted-home-directory.html" rel="alternate"/><published>2017-08-04T00:00:00+02:00</published><updated>2026-01-10T23:56:42+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-08-04:/2017/08/enable-locate-on-systems-with-and-encrypted-home-directory.html</id><summary type="html">&lt;p&gt;If you have an encrypted home directory on a linux system chances are that you are using &lt;span class="docutils literal"&gt;ecryptfs&lt;/span&gt;. This is the simplest way of encrypting the sensitive parts of your computer. You may also have noticed that the &lt;span class="docutils literal"&gt;locate&lt;/span&gt; command does not find files in your home directory any&amp;nbsp;more.&lt;/p&gt;
&lt;p&gt;This behaviour is intentional. &lt;span class="docutils literal"&gt;locate&lt;/span&gt; does not search the disk directly, instead it queries a database file generally called &lt;span class="docutils literal"&gt;mlocate.db&lt;/span&gt; generated by the &lt;span class="docutils literal"&gt;updatedb&lt;/span&gt; tool. This file is updated by the &lt;span class="docutils literal"&gt;updatedb&lt;/span&gt; tool which is usually run with cron and it contains information about every file and folder on …&lt;/p&gt;</summary><content type="html">&lt;p&gt;If you have an encrypted home directory on a linux system chances are that you are using &lt;span class="docutils literal"&gt;ecryptfs&lt;/span&gt;. This is the simplest way of encrypting the sensitive parts of your computer. You may also have noticed that the &lt;span class="docutils literal"&gt;locate&lt;/span&gt; command does not find files in your home directory any&amp;nbsp;more.&lt;/p&gt;
&lt;p&gt;This behaviour is intentional. &lt;span class="docutils literal"&gt;locate&lt;/span&gt; does not search the disk directly, instead it queries a database file generally called &lt;span class="docutils literal"&gt;mlocate.db&lt;/span&gt; generated by the &lt;span class="docutils literal"&gt;updatedb&lt;/span&gt; tool. This file is updated by the &lt;span class="docutils literal"&gt;updatedb&lt;/span&gt; tool which is usually run with cron and it contains information about every file and folder on your system. This might not seem like a big deal but it contents could provide a potential attacker  valuable information about the information stored on your computer. Worse, this file is stored in &lt;span class="docutils literal"&gt;/var/lib&lt;/span&gt;, outside the confines of your encrypted home&amp;nbsp;directory.&lt;/p&gt;
&lt;p&gt;We can however work around this and have the best of both worlds. Maintain the security of the encrypted home and the convenience of &lt;span class="docutils literal"&gt;locate&lt;/span&gt;. To do this we need to create a new &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;mlocate.db&amp;#8220;file&lt;/span&gt; just for our home directory and tell &amp;#8220;locate&lt;/span&gt; to use it&amp;nbsp;too.&lt;/p&gt;
&lt;p&gt;To achieve the first step we will add a new task to our &lt;span class="docutils literal"&gt;crontab&lt;/span&gt;. To edit it run &lt;span class="docutils literal"&gt;crontab &lt;span class="pre"&gt;-e&lt;/span&gt;&lt;/span&gt; as yourself. Do not use &lt;span class="docutils literal"&gt;sudo&lt;/span&gt; since that would cause you to edit the &lt;span class="docutils literal"&gt;crontab&lt;/span&gt; of the root user. Add the following line to the bottom of the&amp;nbsp;file:&lt;/p&gt;
&lt;pre class="literal-block"&gt;00 01 * * * username updatedb --require-visibility 0 --output $HOME/.local/lib/mlocate.db --database-root $HOME/&lt;/pre&gt;
&lt;p&gt;This will run updatedb every every hour. Normally &lt;span class="docutils literal"&gt;updatedb&lt;/span&gt; would refuse to run if the owner of the &lt;span class="docutils literal"&gt;mlocate.db&lt;/span&gt; file is not &lt;span class="docutils literal"&gt;slocate&lt;/span&gt;. The &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;#8212;require-visibility&lt;/span&gt; 0&lt;/span&gt; tells it to sip that check which allows us to run the command as our own user. The &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;#8212;output&lt;/span&gt;&lt;/span&gt; option specifies the location where the database should be saved and the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;#8212;database-root&lt;/span&gt;&lt;/span&gt; makes &lt;span class="docutils literal"&gt;updatedb&lt;/span&gt; only search the specified&amp;nbsp;folder.&lt;/p&gt;
&lt;p&gt;Now that we have a database file we need to help &lt;span class="docutils literal"&gt;locate&lt;/span&gt; find it. We can do this with the &lt;span class="docutils literal"&gt;LOCATE_PATH&lt;/span&gt; environment variable. Add the following line to your &lt;span class="docutils literal"&gt;.bashrc&lt;/span&gt;&lt;/p&gt;
&lt;pre class="literal-block"&gt;# Tell locate to use our very own mlocate database including files indexed here
export LOCATE_PATH=&amp;quot;${HOME}/.local/lib/mlocate.db&amp;quot;&lt;/pre&gt;
&lt;p&gt;There you have it. We&amp;#8217;ve got &lt;span class="docutils literal"&gt;locate&lt;/span&gt; working&amp;nbsp;again.&lt;/p&gt;
</content><category term="misc"/><category term="linux"/></entry><entry><title>The development of the telegraph - An Incomplete time-line</title><link href="https://thibaud.space/2017/05/the-development-of-the-telegraph-an-incomplete-time-line.html" rel="alternate"/><published>2017-05-06T00:00:00+02:00</published><updated>2026-01-11T03:04:44+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-05-06:/2017/05/the-development-of-the-telegraph-an-incomplete-time-line.html</id><summary type="html">&lt;p&gt;Last week I was in London and, true to habit, I went to visit the &lt;a class="reference external" href="https://sciencemuseum.org.uk"&gt;London Science Museum&lt;/a&gt;. I spent a lot of time in the &lt;em&gt;Information Gallery&lt;/em&gt;, specifically the exhibits dedicated to the development of the telegraph.&lt;/p&gt;
&lt;p&gt;The telegraph, for the first time, made it possible to communicate almost instantly over long distances. In addition to speeding up the distribution of information it created a host of other problems. Things that we take for granted today like time zones can be traced directly to the telegraph.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Last week I was in London and, true to habit, I went to visit the &lt;a class="reference external" href="https://sciencemuseum.org.uk"&gt;London Science Museum&lt;/a&gt;. I spent a lot of time in the &lt;em&gt;Information Gallery&lt;/em&gt;, specifically the exhibits dedicated to the development of the&amp;nbsp;telegraph.&lt;/p&gt;
&lt;p&gt;The telegraph, for the first time, made it possible to communicate almost instantly over long distances. In addition to speeding up the distribution of information it created a host of other problems. Things that we take for granted today like time zones can be traced directly to the&amp;nbsp;telegraph.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;The rest of this post is (an incomplete) timeline of the development of the telegraph. Don&amp;#8217;t take this to be a definitive history, it is simply a re-transcription of the notes I took during whilst exploring the&amp;nbsp;exhibit.&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;1835&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Morse makes his first&amp;nbsp;telegraph.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;1840&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;The printing telegraph&amp;nbsp;appears.&lt;/p&gt;
&lt;p&gt;In a printing telegraph, instead of having one button the operator would use a piano like keyboard to send a series of pulses along the cable. A machine on the receiving end would then decode the pulses and print out the text of the message directly without requiring an operator to translate it from Morse&amp;nbsp;code.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;1850&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;First cross channel telegraph wire&amp;nbsp;laid.&lt;/p&gt;
&lt;p&gt;A telegraph cable is laid from Britain to France. It was only about two centimetres in diameter and failed within an hour of completion. A second attempt in 1851 was successful and was used until&amp;nbsp;1875.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;1852&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;The telegraph is used to propagate railway time in&amp;nbsp;England.&lt;/p&gt;
&lt;p&gt;Until railways brought towns closer together in an interconnected mesh each would have it&amp;#8217;s own time derived from the local solar time. Whilst not an issue for towns close to each other it was a problem when the task of expressing the timetables for trains in distant areas of the&amp;nbsp;country.&lt;/p&gt;
&lt;p&gt;The solution was railway time. The railway company expressed all the timetables in London time and kept &amp;#8220;railway time&amp;#8221; clocks in each station. However, precise mechanical clocks were still expensive and keeping the clocks synchronised throughout the country was a real&amp;nbsp;challenge.&lt;/p&gt;
&lt;p&gt;Enter the telegraph. Dedicated telegraph lines were used to connect stations to central clock. Every hour, on the hour, an electrical pulse would be sent on the wires. This allowed distant clocks to be regularly re-synchronised as they drifted away from the &lt;em&gt;official&lt;/em&gt;&amp;nbsp;time.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;1857&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;The first transatlantic cable is&amp;nbsp;laid.&lt;/p&gt;
&lt;p&gt;No ship in the world at the time was large enough to carry the entire length of cable required to cross the Atlantic ocean so instead, two ships were converted, one in England and one in the United-States: the &lt;span class="caps"&gt;HMS&lt;/span&gt; Agamemnon and the &lt;span class="caps"&gt;USS&lt;/span&gt; Niagara. They set sail and met in the middle of the Atlantic to join their cables together. The first two attempts were failed when the cable broke and could not be&amp;nbsp;retrieved.&lt;/p&gt;
&lt;p&gt;A third attempt in 1858 was more successful however, when the two cables were about to be joined it was discovered that the armouring around the cables had been wound in opposite directions and, if directly connected they would unravel each other. A new splicing technique had to be improvised then and there to allow the two cables to be joined. The cable however failed after three weeks. The splice, fortunately, was not at&amp;nbsp;fault.&lt;/p&gt;
&lt;p&gt;In those early days it was impossible to amplify the signal which made the signal incredibly faint even when extremely voltages were used to send messages. To read such faint signals an ingenious invention was used: the &lt;span class="caps"&gt;TODO&lt;/span&gt;: (add wikipedia link) &lt;a class="reference external" href="http://example.com"&gt;mirror galvanometer&lt;/a&gt;. This contraption uses a mirror suspended in the centre of a coil of wire. When a current passes through the coil a magnetic field is generated which deflects the mirror. When a light is shone on the mirror the deflection will cause a large change in the reflection of the&amp;nbsp;light.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;1858&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Punched Tape&amp;nbsp;Systems.&lt;/p&gt;
&lt;p&gt;Very quickly humans were identified as a bottleneck. The telegraph lines could be better be put to use if only messages could be sent faster. This led to the development of mechanical machines to replace telegraph operators. In the punched tape systems an operator would encode a message onto a narrow strip of paper which would then be read by an automated machine. On the receiving end another machine would receive the telegraph and print it out on another strip of paper ready to be decoded by another&amp;nbsp;operator.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;1866&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;First successful transatlantic cable&amp;nbsp;laid.&lt;/p&gt;
&lt;p&gt;After the failures of 1857 and 1858 the interest for transatlantic cables waned. It was only in 1866 that a new cable was laid. This time a single ship, the &lt;span class="caps"&gt;SS&lt;/span&gt; Great Eastern was used. It was a passenger liner specially converted for the mission of cable laying and the only ship afloat capable of carrying the entire spool of&amp;nbsp;cable.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;1884&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;International Meridian&amp;nbsp;Conference.&lt;/p&gt;
&lt;p&gt;The telegraph has made the world smaller, distant areas can now communicate in close to real time and it becomes important to standardise time not only in countries but also world-wide. At the &lt;em&gt;International Meridian Conference&lt;/em&gt; the world accepts the concept of 24 hour days based on the Greenwich meridian. This is the birth of &lt;span class="caps"&gt;GMT&lt;/span&gt;&amp;nbsp;time.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;1897&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Time division multiplexing is used to improve the throughput of individual telegraph&amp;nbsp;lines&lt;/p&gt;
&lt;p&gt;Even with mechanisation there were still ways to increase the amount of messages that could be sent over a single telegraph line. One possibility was a time division multiplexing scheme in which several operators would share a single line. Their consoles were connected to a machine that connected them to the actual cable one by one at fixed intervals. In a scheme with 5 operators for example, each would receive 200 milliseconds every second in which to transmit their message. A machine on the other end would perform the opposite operation to decode the&amp;nbsp;messages.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
</content><category term="misc"/><category term="history"/><category term="telegraph"/></entry><entry><title>List included directories of a target with CMake</title><link href="https://thibaud.space/2017/04/list-included-directories-of-a-target-with-cmake.html" rel="alternate"/><published>2017-04-28T00:00:00+02:00</published><updated>2026-01-10T23:56:42+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-04-28:/2017/04/list-included-directories-of-a-target-with-cmake.html</id><content type="html">&lt;p&gt;When debugging a &lt;span class="docutils literal"&gt;CMake&lt;/span&gt; build file it is often useful to print out all the directories included by a target. The following snippet does just that. Simply replace &lt;span class="docutils literal"&gt;${target_name}&lt;/span&gt; and run &lt;span class="docutils literal"&gt;cmake&lt;/span&gt; to see the list of included&amp;nbsp;files.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;get_property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;includes&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;TARGET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;target_name&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;PROPERTY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;INCLUDE_DIRECTORIES&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;STATUS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Included directories for target ${target_name}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;includes&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;STATUS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;  - ${dir}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;endfor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</content><category term="misc"/><category term="cmake"/><category term="quick-tip"/></entry><entry><title>A very non-comprehensive introduction to ROS package format 2</title><link href="https://thibaud.space/2017/04/a-very-non-comprehensive-introduction-to-ros-package-format-2.html" rel="alternate"/><published>2017-04-25T00:00:00+02:00</published><updated>2026-01-11T03:04:44+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-04-25:/2017/04/a-very-non-comprehensive-introduction-to-ros-package-format-2.html</id><summary type="html">&lt;p&gt;The &lt;span class="caps"&gt;ROS&lt;/span&gt; &lt;a class="reference external" href="http://docs.ros.org/api/catkin/html/howto/format2/index.html"&gt;package format 2&lt;/a&gt; is the new recommended package format for &lt;span class="caps"&gt;ROS&lt;/span&gt; projects. It supersedes the previous package format with a handful of improvements. Let’s take a look at how to structure the new file.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;The &lt;span class="caps"&gt;ROS&lt;/span&gt; &lt;a class="reference external" href="http://docs.ros.org/api/catkin/html/howto/format2/index.html"&gt;package format 2&lt;/a&gt; is the new recommended package format for &lt;span class="caps"&gt;ROS&lt;/span&gt; projects. It supersedes the previous package format with a handful of improvements. Let&amp;#8217;s take a look at how to structure the new&amp;nbsp;file.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;To specify that you are using format 2 you must change the opening &lt;span class="docutils literal"&gt;&amp;lt;package &lt;span class="pre"&gt;&amp;#8230;&amp;gt;&lt;/span&gt;&lt;/span&gt; tag in your &lt;span class="docutils literal"&gt;package.xml&lt;/span&gt;&amp;nbsp;to&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;format=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;2&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;...
&lt;span class="nt"&gt;&amp;lt;/ package&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The changes from format 1 to 2 only affect the tags specifying dependencies. The old &lt;span class="docutils literal"&gt;&amp;lt;build_depend&amp;gt;&lt;/span&gt; and &lt;span class="docutils literal"&gt;&amp;lt;run_depend&amp;gt;&lt;/span&gt; tags are gone now. In there place we have a handful of more explicit&amp;nbsp;tags.&lt;/p&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;&lt;span class="docutils literal"&gt;&amp;lt;build_depend&amp;gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;This tag is used for any system or &lt;span class="caps"&gt;ROS&lt;/span&gt; packages required to build your package. This means that any &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;-dev&lt;/span&gt;&lt;/span&gt; or &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;-devel&lt;/span&gt;&lt;/span&gt; ubuntu package required to compile should be listed&amp;nbsp;here.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;span class="docutils literal"&gt;&amp;lt;build_export_depend&amp;gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;This is the list of packages that other packages that use yours should need. If for example one of your interfaces uses a type defined in another library your users will need to include the headers for that library in their project. This is where you specify&amp;nbsp;that.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;span class="docutils literal"&gt;&amp;lt;exec_depend&amp;gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;This tag is for packages that provide executables or shared libraries required by your program. This is where you specify all the bits and pieces that your program needs to&amp;nbsp;run.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;&lt;span class="docutils literal"&gt;&amp;lt;doc_depend&amp;gt;&lt;/span&gt;&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;This tag is for any packages required to build your documentation (e.g. &lt;span class="docutils literal"&gt;doxygen&lt;/span&gt;).&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;This set of tags is more explicit and solves the ambiguity around the previous &lt;span class="docutils literal"&gt;&amp;lt;run_depend&amp;gt;&lt;/span&gt;&amp;nbsp;tag.&lt;/p&gt;
</content><category term="misc"/><category term="ros"/></entry><entry><title>Lobster, a simple template engine for MATLAB</title><link href="https://thibaud.space/2017/04/lobster-a-simple-template-engine-for-matlab.html" rel="alternate"/><published>2017-04-17T00:00:00+02:00</published><updated>2026-01-11T03:04:44+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-04-17:/2017/04/lobster-a-simple-template-engine-for-matlab.html</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://github.com/comkieffer/Lobster-Templates"&gt;Lobster&lt;/a&gt; is a template engine for &lt;span class="caps"&gt;MATLAB&lt;/span&gt; with a syntax quite similar to &lt;a class="reference external" href="http://jinja.pocoo.org/"&gt;Jinja&lt;/a&gt;, a template engine implemented in python. A template engine is useful if you need to create documents with a repetitive syntax. I use them for example to&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;generate C++ to convert enumeration fields to and from strings,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;generate &lt;span class="caps"&gt;HTML&lt;/span&gt; content&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;generate latex contents from &lt;span class="caps"&gt;MATLAB&lt;/span&gt; variables.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://github.com/comkieffer/Lobster-Templates"&gt;Lobster&lt;/a&gt; is a template engine for &lt;span class="caps"&gt;MATLAB&lt;/span&gt; with a syntax quite similar to &lt;a class="reference external" href="http://jinja.pocoo.org/"&gt;Jinja&lt;/a&gt;, a template engine implemented in python. A template engine is useful if you need to create documents with a repetitive syntax. I use them for example&amp;nbsp;to&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;generate C++ to convert enumeration fields to and from&amp;nbsp;strings,&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;generate &lt;span class="caps"&gt;HTML&lt;/span&gt;&amp;nbsp;content&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;generate latex contents from &lt;span class="caps"&gt;MATLAB&lt;/span&gt;&amp;nbsp;variables.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;Let&amp;#8217;s start with a simple example of the syntax. Create a file called &lt;span class="docutils literal"&gt;template.tpl&lt;/span&gt; with the following&amp;nbsp;contents&lt;/p&gt;
&lt;pre class="literal-block"&gt;Welcome {{ FirstName }},

Your grocery list contains:

{% for item in groceries %}
- {{ item.name }} {% if item.quantity &amp;gt; 1 %}(x{{ item.quantity}}){% endif %}
{% endfor %}&lt;/pre&gt;
&lt;p&gt;If you&amp;#8217;ve downloaded Lobster and added it to your path then using the template is as simple&amp;nbsp;as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;FirstName&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Thibaud&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Groceries&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Milk&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Eggs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Bacon&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Nutella&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Lobster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;template.tpl&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The rendered template is stored in the &lt;span class="docutils literal"&gt;template&lt;/span&gt; variable. What you do with it is up to you. The full documentation can be found in the &lt;a class="reference external" href="Lobster"&gt;github repo&lt;/a&gt;.&lt;/p&gt;
</content><category term="misc"/><category term="matlab"/><category term="lobster"/></entry><entry><title>Matlab, Linux &amp; Serial Ports</title><link href="https://thibaud.space/2017/04/matlab-linux-serial-ports.html" rel="alternate"/><published>2017-04-17T00:00:00+02:00</published><updated>2026-01-11T03:04:44+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-04-17:/2017/04/matlab-linux-serial-ports.html</id><summary type="html">&lt;p&gt;&lt;span class="caps"&gt;MATLAB&lt;/span&gt; on linux follows Ubuntu &lt;span class="caps"&gt;LTS&lt;/span&gt; releases and as such can be quite behind on more recent changes in the linux ecosystem. One recent-ish change that &lt;span class="caps"&gt;MATLAB&lt;/span&gt; still struggles with is the new naming convention for &lt;span class="caps"&gt;USB&lt;/span&gt;-Serial ports. In previous versions of Ubuntu these emulated serial ports were named &lt;span class="docutils literal"&gt;/dev/ttyUSBX&lt;/span&gt; where &lt;span class="docutils literal"&gt;X&lt;/span&gt; is an integer. In newer Ubuntu versions however, these ports are named &lt;span class="docutils literal"&gt;/dev/ttyACMX&lt;/span&gt;, a change that &lt;span class="caps"&gt;MATLAB&lt;/span&gt; has not caught on to yet.&lt;/p&gt;
&lt;p&gt;This seemingly small change means that, out-of-the-box, &lt;span class="caps"&gt;USB&lt;/span&gt;-Serial ports will not work at all in &lt;span class="caps"&gt;MATLAB&lt;/span&gt;. The &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;instrhwinfo(‘serial’)&lt;/span&gt;&lt;/span&gt; command will never see the serial port. Fortunately the fix is quite simple if obscure.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;span class="caps"&gt;MATLAB&lt;/span&gt; on linux follows Ubuntu &lt;span class="caps"&gt;LTS&lt;/span&gt; releases and as such can be quite behind on more recent changes in the linux ecosystem. One recent-ish change that &lt;span class="caps"&gt;MATLAB&lt;/span&gt; still struggles with is the new naming convention for &lt;span class="caps"&gt;USB&lt;/span&gt;-Serial ports. In previous versions of Ubuntu these emulated serial ports were named &lt;span class="docutils literal"&gt;/dev/ttyUSBX&lt;/span&gt; where &lt;span class="docutils literal"&gt;X&lt;/span&gt; is an integer. In newer Ubuntu versions however, these ports are named &lt;span class="docutils literal"&gt;/dev/ttyACMX&lt;/span&gt;, a change that &lt;span class="caps"&gt;MATLAB&lt;/span&gt; has not caught on to&amp;nbsp;yet.&lt;/p&gt;
&lt;p&gt;This seemingly small change means that, out-of-the-box, &lt;span class="caps"&gt;USB&lt;/span&gt;-Serial ports will not work at all in &lt;span class="caps"&gt;MATLAB&lt;/span&gt;. The &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;instrhwinfo(&amp;#8216;serial&amp;#8217;)&lt;/span&gt;&lt;/span&gt; command will never see the serial port. Fortunately the fix is quite simple if&amp;nbsp;obscure.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;You must create a &lt;span class="docutils literal"&gt;java.opts&lt;/span&gt; file in the same directory as your &lt;span class="caps"&gt;MATLAB&lt;/span&gt; binary with the following&amp;nbsp;contents:&lt;/p&gt;
&lt;pre class="literal-block"&gt;-Dgnu.io.rxtx.SerialPorts=/dev/ttyACM0:/dev/ttyACM1:/dev/ttyACM2&lt;/pre&gt;
&lt;p&gt;This simply tells &lt;span class="caps"&gt;MATLAB&lt;/span&gt; that serial ports are named &lt;span class="docutils literal"&gt;ttyACM0&lt;/span&gt;, &lt;span class="docutils literal"&gt;ttyACM1&lt;/span&gt; and &lt;span class="docutils literal"&gt;ttyACM2&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;If you installed &lt;span class="caps"&gt;MATLAB&lt;/span&gt; globally (&lt;em&gt;i.e&lt;/em&gt; with &lt;span class="docutils literal"&gt;sudo&lt;/span&gt;) the &lt;span class="caps"&gt;MATLAB&lt;/span&gt; executable should be in &lt;span class="docutils literal"&gt;/usr/local/&lt;span class="caps"&gt;MATLAB&lt;/span&gt;/R20XXx/bin/glnx64/&lt;/span&gt;. This is the directory in which to put the &lt;span class="docutils literal"&gt;java.opts&lt;/span&gt; file. Remember to change the path to point to your specific &lt;span class="caps"&gt;MATLAB&lt;/span&gt;&amp;nbsp;version.&lt;/p&gt;
&lt;p&gt;You might also want to check out my post on the first things I do after installing &lt;span class="caps"&gt;MATLAB&lt;/span&gt; on a linux computer &lt;a class="reference external" href="matlab_linux_tips"&gt;here&lt;/a&gt; (short version: install &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;matlab-support&lt;/span&gt;&lt;/span&gt;, fix shortcuts, autosave, date format, default browser &lt;span class="amp"&gt;&amp;amp;&lt;/span&gt; last working&amp;nbsp;folder).&lt;/p&gt;
&lt;span class="target" id="matlab-linux-tips"&gt;&lt;/span&gt;</content><category term="misc"/><category term="matlab"/><category term="linux"/><category term="serial"/><category term="quick-tip"/></entry><entry><title>CLion &amp; ROS</title><link href="https://thibaud.space/2017/04/clion-ros.html" rel="alternate"/><published>2017-04-16T00:00:00+02:00</published><updated>2026-01-11T03:04:44+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-04-16:/2017/04/clion-ros.html</id><summary type="html">&lt;p&gt;&lt;a class="reference external" href="https://www.jetbrains.com/clion"&gt;CLion&lt;/a&gt; is a great C++ &lt;span class="caps"&gt;IDE&lt;/span&gt;. It also has the advantage of being cross platform and working on Linux. It has become my &lt;span class="caps"&gt;IDE&lt;/span&gt; of choice on that platform. In this post  I’ll be sharing a couple of tips to use more productively with &lt;span class="caps"&gt;ROS&lt;/span&gt;.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="https://www.jetbrains.com/clion"&gt;CLion&lt;/a&gt; is a great C++ &lt;span class="caps"&gt;IDE&lt;/span&gt;. It also has the advantage of being cross platform and working on Linux. It has become my &lt;span class="caps"&gt;IDE&lt;/span&gt; of choice on that platform. In this post  I&amp;#8217;ll be sharing a couple of tips to use more productively with &lt;span class="caps"&gt;ROS&lt;/span&gt;.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;section id="sourcing-the-ros-environment"&gt;
&lt;h2&gt;Sourcing the &lt;span class="caps"&gt;ROS&lt;/span&gt;&amp;nbsp;environment&lt;/h2&gt;
&lt;p&gt;Unfortunately CLion does not natively understand &lt;span class="caps"&gt;ROS&lt;/span&gt;. This can however be solved quite easily, we just need to change the &lt;span class="docutils literal"&gt;.desktop&lt;/span&gt; file used to launch it so that it sources the &lt;span class="caps"&gt;ROS&lt;/span&gt; workspace. You can find it in &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;~/.local/share/applications/jetbrains-clion.desktop&lt;/span&gt;&lt;/span&gt;. If the file does not exist you can generate it from CLion by clicking &lt;span class="docutils literal"&gt;Create Desktop Entry&lt;/span&gt; in the &lt;span class="docutils literal"&gt;Tools&lt;/span&gt;&amp;nbsp;menu.&lt;/p&gt;
&lt;p&gt;The launcher should look like&amp;nbsp;this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;[Desktop Entry]
Version=1.0
Type=Application
Name=CLion
Icon=/home/YOUR-HOME-DIR/ ... /.icon.svg
Exec=&amp;quot;/home/YOUR-HOME-DIR/ ... /bin/clion.sh&amp;quot; %f
Comment=A smart cross-platform IDE for C and C++
Categories=Development;IDE;
Terminal=false
StartupWMClass=jetbrains-clion&lt;/pre&gt;
&lt;p&gt;To let CLion know about &lt;span class="caps"&gt;ROS&lt;/span&gt; we need a way to pass it the environment variables that get set by &lt;span class="caps"&gt;ROS&lt;/span&gt;. If you source a ros &lt;span class="docutils literal"&gt;setup.bash&lt;/span&gt; file in your &lt;span class="docutils literal"&gt;.bashrc&lt;/span&gt; then we just need to run CLion through bash to make it work. In this case just modify the &lt;span class="docutils literal"&gt;Exec=&lt;/span&gt; line adding &lt;span class="docutils literal"&gt;bash &lt;span class="pre"&gt;-i&lt;/span&gt; &lt;span class="pre"&gt;-c&lt;/span&gt;&lt;/span&gt; to the&amp;nbsp;beginning.:&lt;/p&gt;
&lt;pre class="literal-block"&gt;Exec=bash -i -c &amp;quot;/home/YOUR-HOME-DIR/ ... /bin/clion.sh&amp;quot; %f&lt;/pre&gt;
&lt;p&gt;If you installed CLion using the jetbrains toolbox app the toolbox will overwrite the launcher at every boot. There are two workarounds to&amp;nbsp;this:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;make the launcher read-only with &lt;span class="docutils literal"&gt;chattr +i &lt;span class="pre"&gt;~/local/share/applications/jetbrains-clion.desktop&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;make a copy of the launcher&amp;nbsp;with&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;opted for the second option. I made a copy of the launcher, changed the &lt;span class="docutils literal"&gt;Exec&lt;/span&gt; line and changed the &lt;span class="docutils literal"&gt;Name&lt;/span&gt; line to &lt;span class="docutils literal"&gt;Name=CLion (&lt;span class="caps"&gt;ROS&lt;/span&gt;&amp;nbsp;Kinetic)&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you don&amp;#8217;t source the &lt;span class="caps"&gt;ROS&lt;/span&gt; &lt;span class="docutils literal"&gt;setup.bash&lt;/span&gt; file from your &lt;span class="docutils literal"&gt;.bashrc&lt;/span&gt; you can still get this to work. We just need to sourced it manually as part of the &lt;span class="docutils literal"&gt;Exec&lt;/span&gt; command. If your &lt;span class="caps"&gt;ROS&lt;/span&gt;  workspace is in &lt;span class="docutils literal"&gt;~/development/ros_ws/&lt;/span&gt; then change the &lt;span class="docutils literal"&gt;Exec&lt;/span&gt; line to&amp;nbsp;:&lt;/p&gt;
&lt;pre class="literal-block"&gt;Exec=bash -i -c &amp;quot;source ~/development/ros_ws/devel/setup.bash; /home/YOUR-HOME-DIR/ ... /bin/clion.sh&amp;quot; %f&lt;/pre&gt;
&lt;p&gt;You might have to logout and login again before the changes take&amp;nbsp;effect.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="registering-launch-files-as-xml"&gt;
&lt;h2&gt;Registering &lt;span class="docutils literal"&gt;.launch&lt;/span&gt; files as &lt;span class="docutils literal"&gt;xml&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;To get good syntax highlighting and completion in &lt;span class="docutils literal"&gt;.launch&lt;/span&gt; files you will need to Clion that these are actually &lt;span class="docutils literal"&gt;xml&lt;/span&gt; files. You can do this by opening &lt;span class="docutils literal"&gt;File&lt;/span&gt; &amp;gt; &lt;span class="docutils literal"&gt;Settings&lt;/span&gt; and looking for &lt;span class="docutils literal"&gt;File Types&lt;/span&gt; in the &lt;span class="docutils literal"&gt;Editor&lt;/span&gt; menu. Simply add &lt;span class="docutils literal"&gt;*.launch&lt;/span&gt; to the &lt;em&gt;&lt;span class="caps"&gt;XML&lt;/span&gt;&lt;/em&gt;&amp;nbsp;section.&lt;/p&gt;
&lt;/section&gt;
</content><category term="misc"/><category term="ros"/><category term="clion"/><category term="c++"/></entry><entry><title>Setting up CMake and Doxygen</title><link href="https://thibaud.space/2017/04/setting-up-cmake-and-doxygen.html" rel="alternate"/><published>2017-04-16T00:00:00+02:00</published><updated>2026-01-11T03:04:44+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-04-16:/2017/04/setting-up-cmake-and-doxygen.html</id><summary type="html">&lt;p&gt;Setting up CMake to build your documentation with Doxygen is really quite simple. All you really need is&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;find_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;Doxygen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOXYGEN_FOUND&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;add_custom_target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;doc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ALL&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;COMMAND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOXYGEN_EXECUTABLE&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Doxyfile.h&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;WORKING_DIRECTORY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_CURRENT_SOURCE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;COMMENT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Generating API documentation with Doxygen"&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;VERBATIM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Doxygen Found&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</summary><content type="html">&lt;p&gt;Setting up CMake to build your documentation with Doxygen is really quite simple. All you really need&amp;nbsp;is&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;find_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;Doxygen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOXYGEN_FOUND&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;add_custom_target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;doc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ALL&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;COMMAND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOXYGEN_EXECUTABLE&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Doxyfile.h&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;WORKING_DIRECTORY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_CURRENT_SOURCE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;COMMENT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Generating API documentation with Doxygen&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;VERBATIM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Doxygen Found&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;This adds a &lt;span class="docutils literal"&gt;doc&lt;/span&gt; command that builds the documentation and includes it in the &lt;span class="docutils literal"&gt;&lt;span class="caps"&gt;ALL&lt;/span&gt;&lt;/span&gt; rule so that it will be built by default. However, we can make this much smarter. For instance, we can easily make &lt;span class="docutils literal"&gt;doxygen&lt;/span&gt; entirely optional by only enabling it with a CMake&amp;nbsp;option.&lt;/p&gt;
&lt;p&gt;Here is the snippet I use in all of my projects now. It uses CMake to generate the &lt;span class="docutils literal"&gt;Doxyfile&lt;/span&gt;&amp;nbsp;with:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;a version number set by the &lt;span class="docutils literal"&gt;VERSION_MAJOR&lt;/span&gt;, &lt;span class="docutils literal"&gt;VERSION_MINOR&lt;/span&gt; and &lt;span class="docutils literal"&gt;VERSION_PATH&lt;/span&gt;&amp;nbsp;variables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Only enable Doxygen if explicitly requested by setting the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;-DBUILD_DOCUMENTATION&lt;/span&gt;&lt;/span&gt; option on the command&amp;nbsp;line&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An install&amp;nbsp;target&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To use it, just copy and paste it into your &lt;span class="docutils literal"&gt;CMakeLists.txt&lt;/span&gt;&amp;nbsp;file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;# The project version number.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;VERSION_MAJOR&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;CACHE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;STRING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Project major version number.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;VERSION_MINOR&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;CACHE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;STRING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Project minor version number.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;VERSION_PATCH&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;CACHE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;STRING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Project patch version number.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;mark_as_advanced&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;VERSION_MAJOR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;VERSION_MINOR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;VERSION_PATCH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;find_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;Doxygen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;BUILD_DOCUMENTATION&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Create and install the HTML based API documentation (requires Doxygen)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOXYGEN_FOUND&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;BUILD_DOCUMENTATION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;DOXYGEN_FOUND&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;FATAL_ERROR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Doxygen is needed to build the documentation.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;doxyfile_in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_CURRENT_SOURCE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/Doxyfile.in&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;doxyfile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_CURRENT_SOURCE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/Doxyfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;configure_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;doxyfile_in&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;doxyfile&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;@ONLY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;add_custom_target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;doc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ALL&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;COMMAND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOXYGEN_EXECUTABLE&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;doxyfile&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;WORKING_DIRECTORY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_CURRENT_SOURCE_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;COMMENT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Generating API documentation with Doxygen&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;VERBATIM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;DIRECTORY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_CURRENT_BINARY_DIR&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/html&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="s"&gt;DESTINATION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;share/doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</content><category term="misc"/><category term="c++"/><category term="cmake"/><category term="doxygen"/></entry><entry><title>Useful snippets for ROS</title><link href="https://thibaud.space/2017/04/useful-snippets-for-ros.html" rel="alternate"/><published>2017-04-11T00:00:00+02:00</published><updated>2026-01-10T23:56:42+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-04-11:/2017/04/useful-snippets-for-ros.html</id><summary type="html">&lt;section id="set-cmake-options-in-catkin-build"&gt;
&lt;h2&gt;Set CMake options in &lt;span class="docutils literal"&gt;catkin&amp;nbsp;build&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;To pass CMake options to the &lt;span class="docutils literal"&gt;catkin build&lt;/span&gt; tool simply use the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;#8212;cmake-args&lt;/span&gt;&lt;/span&gt; command line switch. For example to set the &lt;span class="docutils literal"&gt;CMAKE_BUILD_TYPE&lt;/span&gt; I would&amp;nbsp;use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;catkin&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--cmake-args&lt;span class="w"&gt; &lt;/span&gt;-DCMAKE_BUILD_TYPE&lt;span class="o"&gt;=&lt;/span&gt;Debug
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To save the so that they are added for every call you can use the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;#8212;save-config&lt;/span&gt;&lt;/span&gt; command line option&amp;nbsp;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;catkin&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--save-CONFIG&lt;span class="w"&gt; &lt;/span&gt;--cmake-args&lt;span class="w"&gt; &lt;/span&gt;-DCMAKE_BUILD_TYPE&lt;span class="o"&gt;=&lt;/span&gt;Debug
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="build-a-single-package"&gt;
&lt;h2&gt;Build a single&amp;nbsp;package&lt;/h2&gt;
&lt;p&gt;To build a single package just specify its name at after &lt;span class="docutils literal"&gt;catkin build&lt;/span&gt;, &lt;em&gt;e.g.&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;catkin&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;my_package
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There is also an even more convenient form. If you are in the package directory then you …&lt;/p&gt;&lt;/section&gt;</summary><content type="html">&lt;section id="set-cmake-options-in-catkin-build"&gt;
&lt;h2&gt;Set CMake options in &lt;span class="docutils literal"&gt;catkin&amp;nbsp;build&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;To pass CMake options to the &lt;span class="docutils literal"&gt;catkin build&lt;/span&gt; tool simply use the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;#8212;cmake-args&lt;/span&gt;&lt;/span&gt; command line switch. For example to set the &lt;span class="docutils literal"&gt;CMAKE_BUILD_TYPE&lt;/span&gt; I would&amp;nbsp;use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;catkin&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--cmake-args&lt;span class="w"&gt; &lt;/span&gt;-DCMAKE_BUILD_TYPE&lt;span class="o"&gt;=&lt;/span&gt;Debug
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To save the so that they are added for every call you can use the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;#8212;save-config&lt;/span&gt;&lt;/span&gt; command line option&amp;nbsp;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;catkin&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--save-CONFIG&lt;span class="w"&gt; &lt;/span&gt;--cmake-args&lt;span class="w"&gt; &lt;/span&gt;-DCMAKE_BUILD_TYPE&lt;span class="o"&gt;=&lt;/span&gt;Debug
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="build-a-single-package"&gt;
&lt;h2&gt;Build a single&amp;nbsp;package&lt;/h2&gt;
&lt;p&gt;To build a single package just specify its name at after &lt;span class="docutils literal"&gt;catkin build&lt;/span&gt;, &lt;em&gt;e.g.&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;catkin&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;my_package
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There is also an even more convenient form. If you are in the package directory then you can simply&amp;nbsp;use&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;catkin&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--this
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To just re-build the package without touching any of its dependencies&amp;nbsp;use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;catkin&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--this&lt;span class="w"&gt; &lt;/span&gt;--no-deps
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;#8212;this&lt;/span&gt;&lt;/span&gt; switch can also, quite conveniently, be used for &lt;span class="docutils literal"&gt;catkin clean&lt;/span&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="create-a-new-package"&gt;
&lt;h2&gt;Create a new&amp;nbsp;package&lt;/h2&gt;
&lt;p&gt;To create a new &lt;span class="caps"&gt;ROS&lt;/span&gt; package with all the boilerplate just&amp;nbsp;use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;catkin&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;pkg&lt;span class="w"&gt; &lt;/span&gt;PACKAGE_NAME
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Most of the options of the &lt;span class="docutils literal"&gt;package.xml&lt;/span&gt; file can also be set from the command&amp;nbsp;line.&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;-l&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Add&amp;nbsp;licence&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;-a&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Add author in &amp;#8220;First Name, Last Name&amp;#8221; email&amp;nbsp;format&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;-m&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Add&amp;nbsp;maintainer&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;An example invocation using all of these parameters&amp;nbsp;is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;catkin&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;pkg&lt;span class="w"&gt; &lt;/span&gt;demo&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;MIT&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Thibaud Chupin&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;thibaud.chupin@mail.com
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="change-the-console-log-format"&gt;
&lt;h2&gt;Change the console log&amp;nbsp;format&lt;/h2&gt;
&lt;p&gt;The default format string for logs in &lt;span class="caps"&gt;ROS&lt;/span&gt; is quite poor. I usually want to know what node produced each message for example. Changing it is quite simple fortunately, we just need to override the &lt;span class="docutils literal"&gt;ROSCONSOLE_FORMAT&lt;/span&gt; shell variable. The format string I use now&amp;nbsp;is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ROSCONSOLE_FORMAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;[${severity}] &amp;lt;${time}&amp;gt; ${node} @ ${file}:${line}&lt;/span&gt;
&lt;span class="s1"&gt;    ${message}&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note the new line! This produces logs that have the following&amp;nbsp;appearance.&lt;/p&gt;
&lt;pre class="literal-block"&gt;[ INFO] &amp;lt;1494230701.053693261&amp;gt; /camera_left_extrinsic &amp;#64; /home/me/Projects/Nearlab/ros_ws/src/aruco_extrinsic/src/ArucoBoardDetector.cpp:26
    Listening for camera images on /lappycam/image_rect_color
[ INFO] &amp;lt;1494230701.060647944&amp;gt; /camera_left_extrinsic &amp;#64; /home/me/Projects/Nearlab/ros_ws/src/aruco_extrinsic/src/ArucoBoardDetector.cpp:43
    Publishing camera pose to /lappycam/pose
[ INFO] &amp;lt;1494230701.063317878&amp;gt; /camera_left_extrinsic &amp;#64; /home/me/Projects/Nearlab/ros_ws/src/aruco_extrinsic/src/ArucoBoardDetector.cpp:49
    Publishing detection images on /lappycam/detections
[ INFO] &amp;lt;1494230701.356161276&amp;gt; /camera_left_extrinsic &amp;#64; /home/me/Projects/Nearlab/ros_ws/src/aruco_extrinsic/src/ArucoBoardDetector.cpp:55
    Publishing detected marker poses and ids on /lappycam/markers
[ INFO] &amp;lt;1494230701.357764129&amp;gt; /camera_left_extrinsic &amp;#64; /home/me/Projects/Nearlab/ros_ws/src/aruco_extrinsic/src/ArucoBoardDetector.cpp:59
    Aruco board detector ready.
[ INFO] &amp;lt;1494230701.362489914&amp;gt; /camera_left_extrinsic &amp;#64; /home/me/Projects/Nearlab/ros_ws/src/aruco_extrinsic/src/ArucoBoardDetector.cpp:66
    Aruco board parameters: size = 6x4, length = 0.0222, sep = 0.004, dictionary = 1&lt;/pre&gt;
&lt;p&gt;The full list of variables that can be used in the &lt;span class="caps"&gt;ROS&lt;/span&gt; console format string cane be found in the &lt;a class="reference external" href="http://wiki.ros.org/rosconsole#Console_Output_Formatting"&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="debugging-a-node-launched-with-rosrun"&gt;
&lt;h2&gt;Debugging a node launched with &lt;span class="docutils literal"&gt;rosrun&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;To run &lt;span class="docutils literal"&gt;gdb&lt;/span&gt; on a node launched with &lt;span class="docutils literal"&gt;rosrun&lt;/span&gt; simply add &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;#8212;prefix&lt;/span&gt; &amp;#8216;gdb &lt;span class="pre"&gt;-ex&lt;/span&gt; run&amp;#8217;&lt;/span&gt; to the command line. This will start gdb and run the&amp;nbsp;node.&lt;/p&gt;
&lt;p&gt;A complete invocation might look something like&amp;nbsp;this:&lt;/p&gt;
&lt;pre class="literal-block"&gt;rosrun --prefix 'gdb -ex run' my_pkg my_node&lt;/pre&gt;
&lt;/section&gt;
&lt;section id="debugging-a-node-launched-with-roslaunch"&gt;
&lt;h2&gt;Debugging a node launched with &lt;span class="docutils literal"&gt;roslaunch&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;With roslaunch we don&amp;#8217;t control the command line like when we start the node with &lt;span class="docutils literal"&gt;rosrun&lt;/span&gt; instead we can pass &lt;span class="docutils literal"&gt;gdb&lt;/span&gt; command with the &lt;span class="docutils literal"&gt;prefix&lt;/span&gt; parameter in the &lt;span class="docutils literal"&gt;&amp;lt;node&amp;gt;&lt;/span&gt;&amp;nbsp;declaration.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;pkg=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;my_pkg&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;my_node&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;node&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;prefix=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;gb -ex run&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/node&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Check out the documentation for &lt;a class="reference external" href="http://wiki.ros.org/roslaunch/XML/node"&gt;node&lt;/a&gt; to see the full list of parameters it&amp;nbsp;accepts.&lt;/p&gt;
&lt;/section&gt;
</content><category term="misc"/><category term="ros"/><category term="cheatsheet"/></entry><entry><title>Common shortcuts for Bash</title><link href="https://thibaud.space/2017/02/common-shortcuts-for-bash.html" rel="alternate"/><published>2017-02-07T00:00:00+01:00</published><updated>2026-01-10T23:58:03+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-02-07:/2017/02/common-shortcuts-for-bash.html</id><content type="html">&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th class="head"&gt;&lt;p&gt;Keys&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Action&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;R&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;reverse-i-search&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;I&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;i-search&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;A&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Move to start of&amp;nbsp;line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;E&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Move to end of&amp;nbsp;line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;B&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Move backward one&amp;nbsp;word&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;F&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Move forward one&amp;nbsp;word&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;K&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Delete everything from the cursor to the end of the&amp;nbsp;line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;U&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Delete everything from the cursor to the start of the&amp;nbsp;line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content><category term="misc"/><category term="cheatsheet"/><category term="linux"/></entry><entry><title>Matlab Live Script Keyboard Shortcuts</title><link href="https://thibaud.space/2017/02/matlab-live-script-keyboard-shortcuts.html" rel="alternate"/><published>2017-02-07T00:00:00+01:00</published><updated>2026-01-10T23:58:03+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2017-02-07:/2017/02/matlab-live-script-keyboard-shortcuts.html</id><content type="html">&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th class="head"&gt;&lt;p&gt;Keys&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Action&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;Alt&lt;/span&gt; &lt;span class="docutils literal"&gt;L&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Insert Title (&lt;span class="docutils literal"&gt;#&lt;/span&gt;)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;Alt&lt;/span&gt; &lt;span class="docutils literal"&gt;H&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Insert Heading (&lt;span class="docutils literal"&gt;##&lt;/span&gt;)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;Alt&lt;/span&gt; &lt;span class="docutils literal"&gt;Enter&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Insert Section Break (&lt;span class="docutils literal"&gt;%%&lt;/span&gt;)&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;I&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Italic&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;B&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Bold&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;U&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Underline&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;M&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Monospaced&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;K&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Hyperlink&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;Alt&lt;/span&gt; &lt;span class="docutils literal"&gt;G&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Latex/Equation&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;&lt;span class="docutils literal"&gt;Ctrl&lt;/span&gt; &lt;span class="docutils literal"&gt;Alt&lt;/span&gt; &lt;span class="docutils literal"&gt;Enter&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Toggle Code/Text&amp;nbsp;Mode&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Title, Heading and Section Break can be triggered without the shortcut by typing &lt;span class="docutils literal"&gt;#&lt;/span&gt;, &lt;span class="docutils literal"&gt;##&lt;/span&gt; and &lt;span class="docutils literal"&gt;%%&lt;/span&gt;&amp;nbsp;respectively.&lt;/p&gt;
</content><category term="misc"/><category term="matlab"/></entry><entry><title>Fixing the MEvent. CASE! error in MATLAB</title><link href="https://thibaud.space/2016/11/fixing-the-mevent-case-error-in-matlab.html" rel="alternate"/><published>2016-11-24T00:00:00+01:00</published><updated>2026-01-11T03:04:44+01:00</updated><author><name>Thibaud</name></author><id>tag:thibaud.space,2016-11-24:/2016/11/fixing-the-mevent-case-error-in-matlab.html</id><summary type="html">&lt;p&gt;This rather annoying issue in &lt;span class="caps"&gt;MATLAB&lt;/span&gt; causes the string &lt;span class="docutils literal"&gt;MEvent. &lt;span class="caps"&gt;CASE&lt;/span&gt;!&lt;/span&gt; to be repeatedly printed to the command window every time you scroll any window in the application. The suggested solution is to run&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;!synclient&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HorizTwoFingerScroll&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;as part of your startup file to disable horizontal scrolling. This however does not work on more recent linux versions because the &lt;span class="docutils literal"&gt;synaptics&lt;/span&gt; touchpad driver is being deprecated in favour of &lt;span class="docutils literal"&gt;libinput&lt;/span&gt;. The new solution to this problem is slightly more complex however.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This rather annoying issue in &lt;span class="caps"&gt;MATLAB&lt;/span&gt; causes the string &lt;span class="docutils literal"&gt;MEvent. &lt;span class="caps"&gt;CASE&lt;/span&gt;!&lt;/span&gt; to be repeatedly printed to the command window every time you scroll any window in the application. The suggested solution is to&amp;nbsp;run&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;!synclient&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HorizTwoFingerScroll&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;as part of your startup file to disable horizontal scrolling. This however does not work on more recent linux versions because the &lt;span class="docutils literal"&gt;synaptics&lt;/span&gt; touchpad driver is being deprecated in favour of &lt;span class="docutils literal"&gt;libinput&lt;/span&gt;. The new solution to this problem is slightly more complex&amp;nbsp;however.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;First we need to find the id of the touchpad device with the &lt;span class="docutils literal"&gt;xinput list&lt;/span&gt; command from a terminal (not the &lt;span class="caps"&gt;MATLAB&lt;/span&gt; command window). You should see something&amp;nbsp;like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;~$&lt;span class="w"&gt; &lt;/span&gt;xinput&lt;span class="w"&gt; &lt;/span&gt;list
⎡&lt;span class="w"&gt; &lt;/span&gt;Virtual&lt;span class="w"&gt; &lt;/span&gt;core&lt;span class="w"&gt; &lt;/span&gt;pointer&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;pointer&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
⎜&lt;span class="w"&gt;   &lt;/span&gt;↳&lt;span class="w"&gt; &lt;/span&gt;Virtual&lt;span class="w"&gt; &lt;/span&gt;core&lt;span class="w"&gt; &lt;/span&gt;XTEST&lt;span class="w"&gt; &lt;/span&gt;pointer&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;slave&lt;span class="w"&gt;  &lt;/span&gt;pointer&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
⎜&lt;span class="w"&gt;   &lt;/span&gt;↳&lt;span class="w"&gt; &lt;/span&gt;SynPS/2&lt;span class="w"&gt; &lt;/span&gt;Synaptics&lt;span class="w"&gt; &lt;/span&gt;TouchPad&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;slave&lt;span class="w"&gt;  &lt;/span&gt;pointer&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
⎜&lt;span class="w"&gt;   &lt;/span&gt;↳&lt;span class="w"&gt; &lt;/span&gt;ELAN&lt;span class="w"&gt; &lt;/span&gt;Touchscreen&lt;span class="w"&gt;                          &lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;slave&lt;span class="w"&gt;  &lt;/span&gt;pointer&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We are interested in the &lt;span class="docutils literal"&gt;SynPS/2 Synaptics TouchPad&lt;/span&gt; which in this case has &lt;span class="docutils literal"&gt;id=13&lt;/span&gt;. We can see the configuration options supported by this device by running&amp;nbsp;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;~$&lt;span class="w"&gt; &lt;/span&gt;xinput&lt;span class="w"&gt; &lt;/span&gt;list-props&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Remember to change &lt;span class="docutils literal"&gt;13&lt;/span&gt;  to the id of the touchpad on your machine ! In the output you should see a line&amp;nbsp;like:&lt;/p&gt;
&lt;pre class="literal-block"&gt;Synaptics Two-Finger Scrolling (283):   1, 1&lt;/pre&gt;
&lt;p&gt;This tells you that two finger scrolling is enabled in the vertical and horizontal directions. To change this run&amp;nbsp;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;~$&lt;span class="w"&gt; &lt;/span&gt;xinput&lt;span class="w"&gt; &lt;/span&gt;set-prop&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Synaptics Two-Finger Scrolling&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you couldn&amp;#8217;t find the &amp;#8220;Two-Finger Scrolling&amp;#8221; line all is not lost. Look for a line related to horizontal scrolling. In my case that&amp;nbsp;was:&lt;/p&gt;
&lt;pre class="literal-block"&gt;libinput Horizontal Scroll Enabled (266):    1&lt;/pre&gt;
&lt;p&gt;and the command used to disable this property&amp;nbsp;is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;~$&lt;span class="w"&gt; &lt;/span&gt;xinput&lt;span class="w"&gt; &lt;/span&gt;set-prop&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;libinput Horizontal Scroll Enabled&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To have this run automatically every time you run &lt;span class="caps"&gt;MATLAB&lt;/span&gt; you can&amp;nbsp;add&lt;/p&gt;
&lt;pre class="literal-block"&gt;!xinput set-prop 13 &amp;quot;libinput Horizontal Scroll Enabled&amp;quot; 0&lt;/pre&gt;
&lt;p&gt;to your startup&amp;nbsp;file.&lt;/p&gt;
</content><category term="misc"/><category term="Matlab"/><category term="Linux"/></entry><entry><title>Tips for MATLAB on Linux</title><link href="https://thibaud.space/2016/11/tips-for-matlab-on-linux.html" rel="alternate"/><published>2016-11-19T00:00:00+01:00</published><updated>2026-01-10T23:56:42+01:00</updated><author><name>Thibaud</name></author><id>tag:thibaud.space,2016-11-19:/2016/11/tips-for-matlab-on-linux.html</id><summary type="html">&lt;p&gt;Setting up &lt;span class="caps"&gt;MATLAB&lt;/span&gt; can be a hassle. On Linux systems some of the defaults settings can be  rather baffling. Here are the first things I do after&amp;nbsp;installing.&lt;/p&gt;
&lt;section id="install-the-matlab-support-package"&gt;
&lt;h2&gt;Install the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;matlab-support&lt;/span&gt;&lt;/span&gt;&amp;nbsp;package&lt;/h2&gt;
&lt;p&gt;After installing &lt;span class="caps"&gt;MATLAB&lt;/span&gt; you may be frustrated to find that it doesn&amp;#8217;t appear in the launcher. To run it you need to open a terminal and manually run the &lt;span class="docutils literal"&gt;matlab&lt;/span&gt; command. On Ubuntu-derived systems the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;matlab-support&lt;/span&gt;&lt;/span&gt; packages solves this. It creates the missing &lt;span class="docutils literal"&gt;.desktop&lt;/span&gt; files to let the launcher know about &lt;span class="caps"&gt;MATLAB&lt;/span&gt; and offers to solve some common problems with &lt;span class="caps"&gt;MATLAB&lt;/span&gt; on Linux.
Pi
To install it …&lt;/p&gt;&lt;/section&gt;</summary><content type="html">&lt;p&gt;Setting up &lt;span class="caps"&gt;MATLAB&lt;/span&gt; can be a hassle. On Linux systems some of the defaults settings can be  rather baffling. Here are the first things I do after&amp;nbsp;installing.&lt;/p&gt;
&lt;section id="install-the-matlab-support-package"&gt;
&lt;h2&gt;Install the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;matlab-support&lt;/span&gt;&lt;/span&gt;&amp;nbsp;package&lt;/h2&gt;
&lt;p&gt;After installing &lt;span class="caps"&gt;MATLAB&lt;/span&gt; you may be frustrated to find that it doesn&amp;#8217;t appear in the launcher. To run it you need to open a terminal and manually run the &lt;span class="docutils literal"&gt;matlab&lt;/span&gt; command. On Ubuntu-derived systems the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;matlab-support&lt;/span&gt;&lt;/span&gt; packages solves this. It creates the missing &lt;span class="docutils literal"&gt;.desktop&lt;/span&gt; files to let the launcher know about &lt;span class="caps"&gt;MATLAB&lt;/span&gt; and offers to solve some common problems with &lt;span class="caps"&gt;MATLAB&lt;/span&gt; on Linux.
Pi
To install it simply&amp;nbsp;run&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;matlab-support
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="change-the-keyboard-shortcuts"&gt;
&lt;h2&gt;Change the keyboard&amp;nbsp;shortcuts&lt;/h2&gt;
&lt;p&gt;On Linux systems &lt;span class="caps"&gt;MATLAB&lt;/span&gt; uses emacs-like keyboard shortcuts. This might be great for emacs power users but makes life harder for the rest of the us. To switch back to more common&amp;nbsp;shortcuts&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Open &lt;span class="caps"&gt;MATLAB&lt;/span&gt;&amp;nbsp;preferences.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the &lt;span class="docutils literal"&gt;&lt;span class="caps"&gt;MATLAB&lt;/span&gt;&lt;/span&gt; tree open &lt;span class="docutils literal"&gt;Keyboard&lt;/span&gt; then &lt;span class="docutils literal"&gt;Shortcuts&lt;/span&gt; and change the &lt;span class="docutils literal"&gt;Active Settings&lt;/span&gt; dropdown from &lt;span class="docutils literal"&gt;Emacs Default Set&lt;/span&gt; to &lt;span class="docutils literal"&gt;Windows Default Set&lt;/span&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To change the default open &lt;span class="docutils literal"&gt;Preferences&lt;/span&gt;. In the &lt;span class="docutils literal"&gt;&lt;span class="caps"&gt;MATLAB&lt;/span&gt;&lt;/span&gt; tree open &lt;span class="docutils literal"&gt;Fonts&lt;/span&gt; and change the &lt;span class="docutils literal"&gt;Desktop Code&amp;nbsp;Font&lt;/span&gt;&lt;/p&gt;
&lt;/section&gt;
&lt;section id="change-the-autosave-location"&gt;
&lt;h2&gt;Change the autosave&amp;nbsp;location&lt;/h2&gt;
&lt;p&gt;&lt;span class="caps"&gt;MATLAB&lt;/span&gt; will helpfully autosave our files for us. By default it does this by creating a copy of the file and appending a &lt;span class="docutils literal"&gt;~&lt;/span&gt; to its name. These files however can create clutter your working directory. An alternative is to instruct &lt;span class="caps"&gt;MATLAB&lt;/span&gt; to save them all in a single&amp;nbsp;folder.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Open &lt;span class="caps"&gt;MATLAB&lt;/span&gt;&amp;nbsp;preferences&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the &lt;span class="docutils literal"&gt;&lt;span class="caps"&gt;MATLAB&lt;/span&gt;&lt;/span&gt; tree open &lt;span class="docutils literal"&gt;Editor/Debugger&lt;/span&gt; then &lt;span class="docutils literal"&gt;Backup Files&lt;/span&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Change the &lt;span class="docutils literal"&gt;Location&lt;/span&gt; option from &lt;span class="docutils literal"&gt;Source file directories&lt;/span&gt; to &lt;span class="docutils literal"&gt;Single directory&lt;/span&gt; and set the target directory in the text box. I usually use a hidden folder in my &lt;span class="caps"&gt;MATLAB&lt;/span&gt; home directory so I set it to &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;~/home/Documents/&lt;span class="caps"&gt;MATLAB&lt;/span&gt;/.autosave&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="changing-the-date-and-time-formats"&gt;
&lt;h2&gt;Changing the date and time&amp;nbsp;formats&lt;/h2&gt;
&lt;p&gt;Whilst America seems proud of its imperial units and weird way of writing dates the rest of world can&amp;#8217;t help but look on with a puzzled expression. By default &lt;span class="caps"&gt;MATLAB&lt;/span&gt; seems to completely disregard de system locale when presenting&amp;nbsp;dates.&lt;/p&gt;
&lt;p&gt;Whilst we can&amp;#8217;t force America to adopt the glorious metric system we can however instruct &lt;span class="caps"&gt;MATLAB&lt;/span&gt; to present dates in a reasonable&amp;nbsp;manner.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Open &lt;span class="caps"&gt;MATLAB&lt;/span&gt;&amp;nbsp;preferences&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the &lt;span class="docutils literal"&gt;&lt;span class="caps"&gt;MATLAB&lt;/span&gt;&lt;/span&gt; tree open &lt;span class="docutils literal"&gt;Command&amp;nbsp;Window&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check the &lt;span class="docutils literal"&gt;Locale&lt;/span&gt; option in the &lt;span class="docutils literal"&gt;Datetime Format&lt;/span&gt; section. If &lt;span class="caps"&gt;MATLAB&lt;/span&gt; was able to locate a sane locale changing it should be enough. In my case &lt;span class="caps"&gt;MATLAB&lt;/span&gt; was unable to locate a normal locale so I had to manually set the &lt;span class="docutils literal"&gt;datetime&lt;/span&gt; and &lt;span class="docutils literal"&gt;date&lt;/span&gt;&amp;nbsp;formats.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set the &lt;span class="docutils literal"&gt;Default date and time format&lt;/span&gt; to &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;uuuu-&lt;span class="caps"&gt;MM&lt;/span&gt;-dd&lt;/span&gt; &lt;span class="caps"&gt;HH&lt;/span&gt;:mm:ss&lt;/span&gt; and the &lt;span class="docutils literal"&gt;Default date only format&lt;/span&gt; to &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;uuuu-&lt;span class="caps"&gt;MM&lt;/span&gt;-dd&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="forcing-matlab-to-use-the-default-browser-for-html-pages"&gt;
&lt;h2&gt;Forcing &lt;span class="caps"&gt;MATLAB&lt;/span&gt; to use the default browser for html&amp;nbsp;pages&lt;/h2&gt;
&lt;p&gt;If you frequently use the &lt;span class="docutils literal"&gt;Publish as &lt;span class="caps"&gt;HTML&lt;/span&gt;&lt;/span&gt; feature of &lt;span class="caps"&gt;MATLAB&lt;/span&gt; to share scripts and their results you may be frustrated that &lt;span class="caps"&gt;MATLAB&lt;/span&gt; doesn&amp;#8217;t us your browser of choice. To force to always use the default browser if you change it later on you can tell &lt;span class="caps"&gt;MATLAB&lt;/span&gt; to use the &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;xdg-open&lt;/span&gt;&lt;/span&gt; command instead. This command always uses the system default apps to open any file&amp;nbsp;type.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Open &lt;span class="caps"&gt;MATLAB&lt;/span&gt;&amp;nbsp;preferences&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the &lt;span class="docutils literal"&gt;&lt;span class="caps"&gt;MATLAB&lt;/span&gt;&lt;/span&gt; tree open &lt;span class="docutils literal"&gt;Web&lt;/span&gt; and in &lt;span class="docutils literal"&gt;System Web Browser&lt;/span&gt; set &lt;span class="docutils literal"&gt;Command&lt;/span&gt; to &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;xdg-open&lt;/span&gt;&lt;/span&gt;. You can also set it as the &lt;span class="docutils literal"&gt;&lt;span class="caps"&gt;PDF&lt;/span&gt; Reader&lt;/span&gt;&amp;nbsp;command.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="last-working-folder"&gt;
&lt;h2&gt;Last working&amp;nbsp;folder&lt;/h2&gt;
&lt;p&gt;It can be frustrating to have to manually navigate to your current project&amp;#8217;s working folder every time you re-open &lt;span class="caps"&gt;MATLAB&lt;/span&gt;. &lt;span class="caps"&gt;MATLAB&lt;/span&gt; can remember your current working folder and re-open it&amp;nbsp;automatically.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Open &lt;span class="caps"&gt;MATLAB&lt;/span&gt;&amp;nbsp;preferences&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the &lt;span class="docutils literal"&gt;&lt;span class="caps"&gt;MATLAB&lt;/span&gt;&lt;/span&gt; tree open &lt;span class="docutils literal"&gt;General&lt;/span&gt; and change the &lt;span class="docutils literal"&gt;Initial Working Folder&lt;/span&gt; to &lt;span class="docutils literal"&gt;Last working folder from previous &lt;span class="caps"&gt;MATLAB&lt;/span&gt; session&lt;/span&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;section id="startup-m-files"&gt;
&lt;h2&gt;&lt;span class="docutils literal"&gt;startup.m&lt;/span&gt;&amp;nbsp;files&lt;/h2&gt;
&lt;p&gt;One of the most underused features of &lt;span class="caps"&gt;MATLAB&lt;/span&gt; is &lt;span class="docutils literal"&gt;startup.m&lt;/span&gt;&amp;nbsp;files.&lt;/p&gt;
&lt;p&gt;When &lt;span class="caps"&gt;MATLAB&lt;/span&gt; starts up it will look for a file named &lt;span class="docutils literal"&gt;startup.m&lt;/span&gt; in the current working directory and run it automatically. You can use it to set project specific settings&amp;nbsp;easily.&lt;/p&gt;
&lt;p&gt;For example, I use it to disable some warnings caused by some tools I use (and can&amp;#8217;t modify) to avoid extraneous noise. Another common use it to load all the subdirectories that contain tools I am using in the current project instead of having to &lt;span class="docutils literal"&gt;addpath()&lt;/span&gt; them&amp;nbsp;manually.&lt;/p&gt;
&lt;p&gt;You can find the startup file I put in the root of my &lt;span class="caps"&gt;MATLAB&lt;/span&gt; projects &lt;a class="reference external" href="https://github.com/comkieffer/MATLAB-Config/blob/master/startup_base.m"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/section&gt;
</content><category term="misc"/><category term="Matlab"/><category term="Linux"/></entry><entry><title>Virtual Reference Feedback Tuning</title><link href="https://thibaud.space/2016/04/virtual-reference-feedback-tuning.html" rel="alternate"/><published>2016-04-29T00:00:00+02:00</published><updated>2026-01-11T03:04:44+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2016-04-29:/2016/04/virtual-reference-feedback-tuning.html</id><summary type="html">&lt;div&gt;&lt;p&gt;&lt;strong&gt;V&lt;/strong&gt;irtual &lt;strong&gt;R&lt;/strong&gt;eference &lt;strong&gt;F&lt;/strong&gt;eedback &lt;strong&gt;T&lt;/strong&gt;uning is a data driven method used to design a controller for an unknown plant based on input/output measurements. The method uses a single set of input/output data generated by the plant without the need for specific experiments or model identification.&lt;/p&gt;
&lt;/div&gt;</summary><content type="html">&lt;div&gt;&lt;p&gt;&lt;strong&gt;V&lt;/strong&gt;irtual &lt;strong&gt;R&lt;/strong&gt;eference &lt;strong&gt;F&lt;/strong&gt;eedback &lt;strong&gt;T&lt;/strong&gt;uning is a data driven method used to design a controller for an unknown plant based on input/output measurements. The method uses a single set of input/output data generated by the plant without the need for specific experiments or model identification.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;This is the first post in a series of posts detailing the method and explaining how to use it in &lt;span class="docutils literal"&gt;&lt;span class="caps"&gt;MATLAB&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Virtual Reference Feedback Tuning (this post)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;span class="caps"&gt;VRFT&lt;/span&gt; in &lt;span class="caps"&gt;MATLAB&lt;/span&gt; - Coming Soon&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;section id="how-does-it-work"&gt;
&lt;h2&gt;How Does It Work ?&lt;/h2&gt;
&lt;p&gt;The general idea behind &lt;span class="caps"&gt;VRFT&lt;/span&gt; is to transform the control problem of designing the controller into an identification problem. To use the &lt;span class="caps"&gt;VRFT&lt;/span&gt; method we need:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;A reference Model. This is a model that describes the ideal behaviour of the closed loop system.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One set of input-output data on the plant. The input does not need to have any special characteristics.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="align-center"&gt;
&lt;img alt="The block diagram of VRFT" src="https://thibaud.space/static/about-vrft/vrft_block_diagram.png" width="1282" height="418"&gt;
&lt;figcaption&gt;
&lt;p&gt;Block diagram showing the closed loop control system and the reference model.&lt;/p&gt;
&lt;div class="legend"&gt;
&lt;p&gt;Note that the output of the reference model and the control loop are identical.&lt;/p&gt;
&lt;/div&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The output of the plant is the known from the I/O data that was previously collected. If we consider these measurements as the output of the reference model then we can build an input signal &lt;span class="math"&gt;\(r(t)\)&lt;/span&gt; such that:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
y(t) = M_R(z) r(t)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;This signal is the &lt;em&gt;virtual reference&lt;/em&gt;. We want to make the closed loop behaviour of the system as close as possible to the reference model so we use this &lt;em&gt;virtual reference&lt;/em&gt; as the set-point for the control loop. The output of the controller is the plant input &lt;span class="math"&gt;\(u(t)\)&lt;/span&gt;. This is the other known signal.&lt;/p&gt;
&lt;p&gt;The input of the controller can easily be calculated:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
e(t) = r(t) - y(t)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Now that both the input and the output (&lt;span class="math"&gt;\(u(t)\)&lt;/span&gt;) of the controller are known the control problem can be reduced to an identification problem.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="a-more-rigorous-approach"&gt;
&lt;h2&gt;A More Rigorous approach&lt;/h2&gt;
&lt;p&gt;It is assumed that the plant is a &lt;em&gt;linear&lt;/em&gt; &lt;span class="caps"&gt;SISO&lt;/span&gt; discrete-time dynamical system described by a rational transfer function (&lt;span class="math"&gt;\(P(z)\)&lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;We choose a reference model &lt;span class="math"&gt;\(M_R(z)\)&lt;/span&gt; that describes the desired behaviour of the closed loop control system.&lt;/p&gt;
&lt;p&gt;We determine a class of controllers &lt;span class="math"&gt;\(C(z; \theta)\)&lt;/span&gt; that depend linearly on the parameter vector &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; such that:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
C(z; \theta) = \beta(z)^T\theta^T
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;span class="math"&gt;\(\beta = [\beta_1(z), \beta_2(z), ..., \beta_n(z)]^T\)&lt;/span&gt; is a vector of linear transfer functions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;span class="math"&gt;\(\theta = [\theta_1, \theta_2, ... \theta_n]^T\)&lt;/span&gt; is the vector of parameters&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The control objective is the minimisation of the following criterion:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{aligned}
    J_{MR}(\theta) &amp;amp;= \left| \left( \frac{P(z)C(z; \theta)}{1 + P(z)C(z; \theta)} - M(z) \right) W(z) \right|_2^2 \\
                   &amp;amp;= \left| \left( T(z) - M(z) \right) W(z) \right|_2^2
\end{aligned}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;which is equivalent to minimising the difference between the closed loop system and the reference model scaled by an appropriate weighting function. The choice of this weighting function &lt;span class="math"&gt;\(W(z)\)&lt;/span&gt; is up to the system designer and can be chosen to emphasize or de-emphasize performance in certain frequency bands.&lt;/p&gt;
&lt;p&gt;An optimal choice of &lt;span class="math"&gt;\(C(z; \theta)\)&lt;/span&gt; will produce a closed loop system whose transfer function is &lt;span class="math"&gt;\(M(z)\)&lt;/span&gt;. Thus, if the closed-loop system is fed a reference signal &lt;span class="math"&gt;\(r(t)\)&lt;/span&gt; its output will be :&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
y(t) = T(z)r(t) = M(z)r(t)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;The &lt;span class="caps"&gt;VRFT&lt;/span&gt; method works by using the measured output (&lt;span class="math"&gt;\(y(t)\)&lt;/span&gt;) to generate a reference signal &lt;span class="math"&gt;\(\bar{r}(t)\)&lt;/span&gt; such that:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
M(z)\bar{r}(t) = y(t)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;This is our so-called &lt;em&gt;virtual&lt;/em&gt; reference, it does not exist in the physical world and is only used a tool to generate the optimal controller. The input of the controller is:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
e(t) = \bar{r}(t) - y(t)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;The optimal controller must be such that when fed the virtual reference it produces the measured input (&lt;span class="math"&gt;\(u(t)\)&lt;/span&gt;). Thus:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
u(t) = C(z; \hat{\theta}) \bar{r}(t)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Where &lt;span class="math"&gt;\(\hat{\theta}\)&lt;/span&gt; is the optimal parameter vector.&lt;/p&gt;
&lt;p&gt;To calculate the optimal parameter vector we can start by attempting to solve a slightly easier problem.&lt;/p&gt;
&lt;p&gt;We apply a filter &lt;span class="math"&gt;\(L(t)\)&lt;/span&gt; to the data. I will explain the reason for this filter and it’s form soon but bear with me in the meantime.&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
e_L(t) = L(z)e(t) \quad, \quad y_L(t) = L(z)e(t)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Now, consider the following performance index:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{aligned}
    J^N_{VR}(\theta) &amp;amp;= \frac{1}{N} \sum_{t=1}^{N} \left( u_L(t) - C(z; \theta) \ e_L(t) \right)^2 \\
                     &amp;amp;= \frac{1}{N} \sum_{t=1}^{N}\left( u_L(t) - \beta^T(z)\theta \ e_L(t) \right)^2 \\
                     &amp;amp;= \frac{1}{N} \sum_{t=1}^{N}\left( u_L(t) - \varphi_L^T(t)\theta \right)^2, \quad \varphi_L = \beta^T(z) \  e_L(t)
\end{aligned}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Since this equation is quadratic in &lt;span class="math"&gt;\(\theta\)&lt;/span&gt; the optimal value &lt;span class="math"&gt;\(\hat{\theta}_N\)&lt;/span&gt; is an explicit function of the data:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\hat{\theta}_N = \left[ \sum_t \phi(t) \phi(t)^T \right]^{-1} \sum_t \phi(t)u(t)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;However, this doesn’t seem to bring us any closer to solving our original problem of minimising &lt;span class="math"&gt;\(J_{MR}\)&lt;/span&gt;. This method though is useful because it is based purely on the data and is relatively easy to compute.&lt;/p&gt;
&lt;p&gt;Before continuing we need to make a digression. Let’s rewrite &lt;span class="math"&gt;\(J_{MR}\)&lt;/span&gt; in the frequency domain:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{aligned}
    J_{MR}(\theta) &amp;amp;= \frac{1}{2 \pi} \cdot \int_{-\pi}^{\pi} \left| \frac{PC(\theta)}{1 + PC(\theta)} - M \right|^2 \left| W \right|^2 d\omega \\
                   &amp;amp;= \frac{1}{2 \pi} \cdot \int_{-\pi}^{\pi} \left| \frac{PC(\theta)}{1 + PC(\theta)} - \frac{PC_0}{1 + PC_0} \right|^2 \left| W \right|^2 d\omega \\
                   &amp;amp;= \frac{1}{2 \pi} \cdot \int_{-\pi}^{\pi} \left| \frac{PC(\theta)[1 + PC_0]     - PC_0[1 + PC(\theta)]}{[1 + PC(\theta)][1 + PC_0]} \right|^2 \left| W \right|^2 d\omega \\
                   &amp;amp;= \frac{1}{2 \pi} \cdot \int_{-\pi}^{\pi} \left| P \cdot \frac{C(\theta) - C_0}{[1 + PC(\theta)][1 + PC_0]} \right|^2 \left| W \right|^2 d\omega \\
\end{aligned}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;We can now go back to &lt;span class="math"&gt;\(J_{VR}\)&lt;/span&gt;.&lt;/p&gt;
&lt;!-- math:.


J^N_{VR}(\theta) &amp;= \frac{1}{N} \sum_{t=1}^{N} \left( u_L(t) - C(z; \theta) \ e_L(t) \right)^2 --&gt;
&lt;p&gt;If &lt;span class="math"&gt;\(u(t)\)&lt;/span&gt; and &lt;span class="math"&gt;\(y(t)\)&lt;/span&gt; can be considered realisations of stationary stochastic processes then as the amount of data grows (&lt;span class="math"&gt;\(N \rightarrow \infty\)&lt;/span&gt;) the following holds:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
J^N_{VR}(\theta) \rightarrow J_{VR}(\theta)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;And:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{aligned}
    J_{VR}(\theta) &amp;amp;= E \left[ \left( u_L(t) - C(z; \theta)e_L(t) \right)^2 \right] \\
                   &amp;amp;= E \left[ L(z) \cdot \left( u(t) - C(z; \theta)e(t) \right)^2 \right]
\end{aligned}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;We would like to get rid of &lt;span class="math"&gt;\(e(t)\)&lt;/span&gt; in the equation to make it depend only on our initial data:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
e(t) = \bar{r}(t) - y(t) = \bar{r}(t) - M_R(z)\bar{r}(t) = \left( 1 - M_R(z) \right) \cdot \bar{r}(t)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;And since:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
r(t) = \frac{y(t)}{M_R} = \frac{P}{M_R} \cdot u(t)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;We can rewrite the criterion as:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{aligned}
    J^N_{VR}(\theta) &amp;amp;= \frac{1}{N} \sum_{t=1}^{N} \left[u_L(t) - C(z; \theta) \cdot \left( 1 - M_R(z) \right) \cdot \frac{P}{M_R} \cdot u_L(t) \right]^2 \\
                     &amp;amp;= \frac{1}{N} \sum_{t=1}^{N} \left[ \left( L(z) \left( 1 - C(z; \theta) \cdot \frac{1 - M_R(z)}{M_R(z)} \cdot P \right) u_L(t)\right)^2 \right]
\end{aligned}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;We also know that, by definition, &lt;span class="math"&gt;\(M_R\)&lt;/span&gt; is:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
M_R(z) = \frac{PC_0}{1 + PC_0} \quad \Rightarrow \quad 1 - M_R = \frac{1 + PC_0 - PC_0}{1 + PC_0} = \frac{1}{1 + PC_0}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Which brings us to:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{aligned}
    1 - C(\theta) \cdot \frac{1 - M_R}{M_R} \cdot P &amp;amp;= \frac{1}{M_R} \left( M_R - PC(\theta) \left[ 1 - M_R \right] \right) \\
        &amp;amp;= \frac{1}{M_R} \left( \frac{PC_0}{1 + PC_0} - \frac{PC(\theta)}{1 + PC_0} \right) \\
        &amp;amp;= \frac{1}{M_R} \left( P \cdot \frac{C_0 - C(\theta)}{1 + PC_0} \right) \\
        &amp;amp;= \frac{1}{M_R} P \cdot \left( C_0 - C(\theta) \right) \left( 1 - M_R \right)
\end{aligned}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;By substituting this result back into &lt;span class="math"&gt;\(J_{VR}\)&lt;/span&gt; we obtain:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
J^N_{VR}(\theta) = E \left[ \left( \frac{L}{M_R} \cdot \left( C_0 - C(\theta) \right) \cdot \left( 1 - M_R \right) \right)^2 \right]
\end{equation*}
&lt;/div&gt;
&lt;p&gt;The frequency-domain representation of this criterion is:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
J_{VR}(\theta) = \frac{1}{2\pi} \int_{-\pi}^{\pi} \left| P \cdot \left( C_0 - C(\theta) \right) \left( 1 - M_R \right) \cdot \frac{L}{M_R} \right|^2 \Phi_u \ d\omega
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Where, &lt;span class="math"&gt;\(\Phi_u\)&lt;/span&gt; is the power density of &lt;span class="math"&gt;\(u(t)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;If &lt;span class="math"&gt;\(C_0(z) \in C(z; \theta)\)&lt;/span&gt; and &lt;span class="math"&gt;\(J_{VR}(\theta)\)&lt;/span&gt; has a unique minimum then minimizing &lt;span class="math"&gt;\(J_{VR}(\theta)\)&lt;/span&gt; will always yield &lt;span class="math"&gt;\({C_0(z)}\)&lt;/span&gt; independently of the value of &lt;span class="math"&gt;\(L(z)\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Notice the similarity between the expressions of &lt;span class="math"&gt;\(J_{MR}\)&lt;/span&gt; and &lt;span class="math"&gt;\(J_{VR}\)&lt;/span&gt;. With a proper choice of the pre-filter &lt;span class="math"&gt;\(L(z)\)&lt;/span&gt; we can make them equal.&lt;/p&gt;
&lt;p&gt;If:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\left| L \right|^2 = \left| \frac{M_R W}{1 + PC(\theta)} \right|^2 \cdot \frac{1}{\Phi_u}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Then,&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{aligned}
    J_{VR}(\theta) &amp;amp;= \frac{1}{2\pi} \int_{-\pi}^{\pi} \left| P \cdot \frac{C_0 - C(\theta)}{1 + PC_0} \cdot \frac{W}{1 + PC(\theta)} \right|^2 \ d\omega \\
                   &amp;amp;= \frac{1}{2 \pi} \int_{-\pi}^{\pi} \left| P \cdot \frac{C(\theta) - C_0}{[1 + PC(\theta)][1 + PC_0]} \right|^2 \left| W \right|^2 \ d\omega = J_{MR}(\theta)
\end{aligned}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Unfortunately this pre-filter is not practical since it depends on knowing the plant model &lt;span class="math"&gt;\(P\)&lt;/span&gt;. However, if &lt;span class="math"&gt;\(C(z; \theta)\)&lt;/span&gt; is a good approximation of &lt;span class="math"&gt;\(C_0(z)\)&lt;/span&gt; then:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\left( 1 - M_R \right) = \frac{1}{1 + PC(z; \theta)} \approx \frac{1}{1 + PC_0(z)}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Thus, &lt;span class="math"&gt;\(L(z)\)&lt;/span&gt; can be written as:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
L(z) = \left| \left(1 - M_R \right) \cdot M_R \cdot W \right|^2 \cdot \frac{1}{\Phi_u}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;This formulation of the pre-filter is completely known. This proves that &lt;span class="math"&gt;\(J_{MR}\)&lt;/span&gt; and &lt;span class="math"&gt;\(J_{VR}\)&lt;/span&gt; are equivalent and that by solving the simpler problem (&lt;span class="math"&gt;\(J_{VR}\)&lt;/span&gt;) we also solve the original problem (&lt;span class="math"&gt;\(J_{MR}\)&lt;/span&gt;).&lt;/p&gt;
&lt;/section&gt;
&lt;section id="recap"&gt;
&lt;h2&gt;Recap&lt;/h2&gt;
&lt;p&gt;In conclusion, the &lt;span class="caps"&gt;VRFT&lt;/span&gt; method can be broken down into a set of relatively simple steps:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Choose a reference model &lt;span class="math"&gt;\(M_R\)&lt;/span&gt; and class of controllers &lt;span class="math"&gt;\(C(z; \theta)\)&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Calculate the pre-filter &lt;span class="math"&gt;\(L(z)\)&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compute the optimal parameter vector &lt;span class="math"&gt;\(\hat{\theta}_N\)&lt;/span&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;span class="caps"&gt;VRFT&lt;/span&gt; method can be extended to cases where the signal is noisy, we will discuss this in a later post. The &lt;span class="caps"&gt;VRFT&lt;/span&gt; method has also been extended to cascade control systems. This too will be the subject of a later post.&lt;/p&gt;
&lt;/section&gt;
&lt;/div&gt;&lt;script type='text/javascript'&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 768) ? 'true' : linebreak;
    }

    var mathjaxscript = document.createElement('script');
    mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
    mathjaxscript.type = 'text/javascript';
    mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML';

    var configscript = document.createElement('script');
    configscript.type = 'text/x-mathjax-config';
    configscript[(window.opera ? "innerHTML" : "text")] =
        "MathJax.Hub.Config({" +
        "    config: ['MMLorHTML.js']," +
        "    TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," +
        "    jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
        "    extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
        "    displayAlign: '"+ align +"'," +
        "    displayIndent: '"+ indent +"'," +
        "    showMathMenu: true," +
        "    messageStyle: 'normal'," +
        "    tex2jax: { " +
        "        inlineMath: [ ['\\\\(','\\\\)'] ], " +
        "        displayMath: [ ['$$','$$'] ]," +
        "        processEscapes: true," +
        "        preview: 'TeX'," +
        "    }, " +
        "    'HTML-CSS': { " +
        "        availableFonts: ['STIX', 'TeX']," +
        "        preferredFont: 'STIX'," +
        "        styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," +
        "        linebreaks: { automatic: "+ linebreak +", width: '90% container' }," +
        "    }, " +
        "}); " +
        "if ('default' !== 'default') {" +
            "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
            "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" +
                "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" +
                "VARIANT['normal'].fonts.unshift('MathJax_default');" +
                "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" +
                "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" +
                "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" +
            "});" +
        "}";

    (document.body || document.getElementsByTagName('head')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="misc"/><category term="automation"/></entry><entry><title>Open In File Browser for Linux</title><link href="https://thibaud.space/2016/03/open-in-file-browser-for-linux.html" rel="alternate"/><published>2016-03-08T00:00:00+01:00</published><updated>2026-01-11T03:04:44+01:00</updated><author><name>Thibaud</name></author><id>tag:thibaud.space,2016-03-08:/2016/03/open-in-file-browser-for-linux.html</id><summary type="html">&lt;p&gt;On windows when right clicking in the file browser pane you can select &lt;span class="docutils literal"&gt;Open in file browser&lt;/span&gt; to open the current folder in windows explorer. On linux you will simply get an error message stating that this action is not supported on this operating system (as of &lt;span class="caps"&gt;MATLAB&lt;/span&gt; 2016a).&lt;/p&gt;
</summary><content type="html">&lt;p&gt;On windows when right clicking in the file browser pane you can select &lt;span class="docutils literal"&gt;Open in file browser&lt;/span&gt; to open the current folder in windows explorer. On linux you will simply get an error message stating that this action is not supported on this operating system (as of &lt;span class="caps"&gt;MATLAB&lt;/span&gt;&amp;nbsp;2016a).&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;The following script provides the same&amp;nbsp;functionality:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="c"&gt;% Open the specified path in the default file browser (linux only)&lt;/span&gt;
&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="c"&gt;% The Unix version of MATLAB does not support &amp;#39;Open in File Browser&amp;#39; so I&lt;/span&gt;
&lt;span class="c"&gt;% implemented a basic version here. All modern linux distributions should&lt;/span&gt;
&lt;span class="c"&gt;% support `xdg-open`&lt;/span&gt;
&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="c"&gt;% To open the MATLAB userpath directory (Documents/MATLAB) run:&lt;/span&gt;
&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="c"&gt;%   &amp;gt;&amp;gt; open_in_file_browser userpath&lt;/span&gt;
&lt;span class="c"&gt;%&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;open_in_file_browser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;path&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="nb"&gt;exist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;var&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;userpath&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;upath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;userpath&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;upath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="nb"&gt;exist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;dir&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;%s is not a folder&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;% To properly redirect stdout and stderr we need to detect the current&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;% shell. Currently we support bash/sh and fish.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;output_redirect_syntax&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&amp;gt; /dev/null 2&amp;gt;&amp;amp;1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;fish&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;output_redirect_syntax&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;&amp;gt; /dev/null ^&amp;amp;1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;system&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;xdg-open &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;output_redirect_syntax&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;xdg-open failed.\n  Status code: %i\n  stdout: %s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c"&gt;% Detect the shell that the command will be run in.&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;shellname&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;% Use -n to not emit a &amp;#39;\n&amp;#39; charracter&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;echo -n $SHELL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Unable to detect shell type.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shellname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;fileparts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The script leverages &lt;span class="docutils literal"&gt;&lt;span class="pre"&gt;xdg-open&lt;/span&gt;&lt;/span&gt; to open the current folder in the default file browser. This should work on any desktop linux&amp;nbsp;distribution.&lt;/p&gt;
</content><category term="misc"/><category term="matlab"/><category term="quick-tip"/></entry><entry><title>Quickly changing projects in MATLAB</title><link href="https://thibaud.space/2016/03/quickly-changing-projects-in-matlab.html" rel="alternate"/><published>2016-03-08T00:00:00+01:00</published><updated>2026-01-11T03:04:44+01:00</updated><author><name>Thibaud</name></author><id>tag:thibaud.space,2016-03-08:/2016/03/quickly-changing-projects-in-matlab.html</id><summary type="html">&lt;p&gt;When working on several different projects in &lt;span class="caps"&gt;MATLAB&lt;/span&gt; changing from one project folder to another can be a hassle. Some projects are in a shared dropbox folders, others are in my main &lt;span class="caps"&gt;MATLAB&lt;/span&gt; directory. Most powerful text editors offer a way of quickly changing project directories without having to navigate the folder structure by hand.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;When working on several different projects in &lt;span class="caps"&gt;MATLAB&lt;/span&gt; changing from one project folder to another can be a hassle. Some projects are in a shared dropbox folders, others are in my main &lt;span class="caps"&gt;MATLAB&lt;/span&gt; directory. Most powerful text editors offer a way of quickly changing project directories without having to navigate the folder structure by&amp;nbsp;hand.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;This is my solution for &lt;span class="caps"&gt;MATLAB&lt;/span&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="c"&gt;% Hacky way of making projects in MATLAB&lt;/span&gt;
&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="c"&gt;% One hassle of MATLAB is that moving from one project to another is a hassle.&lt;/span&gt;
&lt;span class="c"&gt;% With this script you only have to type:&lt;/span&gt;
&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="c"&gt;%   open_project &amp;#39;my project&amp;#39;&lt;/span&gt;
&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="c"&gt;% to move from the current directory to the specified directory. Projects are&lt;/span&gt;
&lt;span class="c"&gt;% simply folders in the one of the &amp;#39;project_folders&amp;#39;.&lt;/span&gt;
&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="c"&gt;% To list all the currently detected projects use:&lt;/span&gt;
&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="c"&gt;%   open_project --list&lt;/span&gt;
&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="c"&gt;% To move to your MATLAB user directory (~/Documents/MATLAB on linux) use:&lt;/span&gt;
&lt;span class="c"&gt;%&lt;/span&gt;
&lt;span class="c"&gt;%   one_project mlhome&lt;/span&gt;
&lt;span class="c"&gt;%&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;open_project&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;project_name&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;project_folders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/home/tibo/Documents/MATLAB/scripts&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;/home/tibo/Dropbox/PoliMi&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;--list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;list_projects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_folders&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;mlhome&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Changing directory to %s\n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;userpath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;% We need to strip the trailing &amp;#39;:&amp;#39; from the userpath&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;user_path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;userpath&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_folders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;fullfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_folders&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;dir&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Found project. Changing directory to %s\n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Unable to locate project %s\n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;list_projects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;project_folders&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_folders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nb"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Projects in &amp;lt;%s&amp;gt;:\n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project_folders&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;folders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_folders&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;j&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;isdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="nb"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;j&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="nb"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;j&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;..&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nb"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;    %s\n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;folders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;j&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After populating the &lt;span class="docutils literal"&gt;project_folders&lt;/span&gt; variables with the directories you use to store your projects you can use &lt;span class="docutils literal"&gt;open_project project_name&lt;/span&gt; to move from on project folder to another. To see the list of directories detected by the tool run &lt;span class="docutils literal"&gt;open_project &lt;span class="pre"&gt;&amp;#8212;list&lt;/span&gt;&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;This will only change the current directory to the project directory. It will not close existing editor tabs or clear the&amp;nbsp;workspace.&lt;/p&gt;
</content><category term="misc"/><category term="matlab"/></entry><entry><title>Quick Tip: Receive a pushbullet notification when a ssh login is successful</title><link href="https://thibaud.space/2016/01/quick-tip-receive-a-pushbullet-notification-when-a-ssh-login-is-successful.html" rel="alternate"/><published>2016-01-19T19:32:00+01:00</published><updated>2026-01-11T03:04:44+01:00</updated><author><name>Thibaud Chupin</name></author><id>tag:thibaud.space,2016-01-19:/2016/01/quick-tip-receive-a-pushbullet-notification-when-a-ssh-login-is-successful.html</id><summary type="html">&lt;p&gt;Pushbullet is a service that allows you to push notifications to your devices. We will be using it to send us a notification any-time somebody successfully connects to our server with ssh.&lt;/p&gt;
&lt;p&gt;The cleanest way to run a script when on ssh logins is through &lt;span class="caps"&gt;PAM&lt;/span&gt;. &lt;span class="caps"&gt;PAM&lt;/span&gt; or “Pluggable Authentication Module” is a library called by all the authentication related functions in Linux that provides a way to hook into the authentication process.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Pushbullet is a service that allows you to push notifications to your devices. We will be using it to send us a notification any-time somebody successfully connects to our server with&amp;nbsp;ssh.&lt;/p&gt;
&lt;p&gt;The cleanest way to run a script when on ssh logins is through &lt;span class="caps"&gt;PAM&lt;/span&gt;. &lt;span class="caps"&gt;PAM&lt;/span&gt; or &amp;#8220;Pluggable Authentication Module&amp;#8221; is a library called by all the authentication related functions in Linux that provides a way to hook into the authentication&amp;nbsp;process.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;The &lt;span class="docutils literal"&gt;pam_exec&lt;/span&gt; module gives us easy access to this system. It allows us to run scripts to react to events like user&amp;nbsp;logins.&lt;/p&gt;
&lt;p&gt;Save this script to &lt;span class="docutils literal"&gt;/opt/ssh_notify/notify.sh&lt;/span&gt;. The script uses the Pushbullet &lt;span class="caps"&gt;API&lt;/span&gt; to send a notification to your phone with the username and &lt;span class="caps"&gt;IP&lt;/span&gt; address for the user that has just signed&amp;nbsp;on.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;PB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;------REPLACE-ME-----------------&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PAM_TYPE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;open_session&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;PAYLOAD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&lt;/span&gt;
&lt;span class="s1"&gt;            &amp;quot;title&amp;quot;: &amp;quot;New shh connection&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;            &amp;quot;body&amp;quot;: &amp;quot;User &amp;lt;%s&amp;gt;, from %s successfully connected at %s to %s.&amp;quot;,&lt;/span&gt;
&lt;span class="s1"&gt;            &amp;quot;type&amp;quot;: &amp;quot;note&amp;quot;&lt;/span&gt;
&lt;span class="s1"&gt;        }&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PAM_USER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PAM_RHOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date&lt;span class="w"&gt; &lt;/span&gt;+%H:%M&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;hostname&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;--header&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Access-Token: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PB_TOKEN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;--header&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Content-Type: application/json&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;--data-binary&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PAYLOAD&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;         &lt;/span&gt;--request&lt;span class="w"&gt; &lt;/span&gt;POST&lt;span class="w"&gt; &lt;/span&gt;https://api.pushbullet.com/v2/pushes
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Before you can start sending notifications you need to retrieve and &lt;span class="caps"&gt;API&lt;/span&gt; access token from the Pushbullet website. Go to your &lt;a class="reference external" href="https://www.pushbullet.com/#settings"&gt;profile&lt;/a&gt; and click on &lt;em&gt;CreateAccessToken&lt;/em&gt;. Copy the token into the space marked &lt;span class="docutils literal"&gt;PB_TOKEN&lt;/span&gt; in the&amp;nbsp;script.&lt;/p&gt;
&lt;p&gt;Now we need to tell &lt;span class="docutils literal"&gt;pam&lt;/span&gt; about our script. Add the following line to the end of &lt;span class="docutils literal"&gt;/etc/pam.d/sshd&lt;/span&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;session&lt;span class="w"&gt; &lt;/span&gt;optional&lt;span class="w"&gt; &lt;/span&gt;pam_exec.so&lt;span class="w"&gt; &lt;/span&gt;/opt/ssh_notify/notify.sh
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To test your changes open a new terminal and ssh into your machine. You should receive a&amp;nbsp;notification.&lt;/p&gt;
&lt;p&gt;The &lt;span class="docutils literal"&gt;pam_exec&lt;/span&gt; environment exposes several other variables that you can use to build a more descriptive message. You can check out the complete list &lt;a class="reference external" href="http://www.linux-pam.org/Linux-PAM-html/sag-pam_exec.html"&gt;here&lt;/a&gt;.&lt;/p&gt;
</content><category term="misc"/><category term="Linux"/></entry><entry><title>Exporting MATLAB figures to LaTeX</title><link href="https://thibaud.space/2015/10/exporting-matlab-figures-to-latex.html" rel="alternate"/><published>2015-10-14T14:31:00+02:00</published><updated>2026-01-11T03:04:44+01:00</updated><author><name>Thibaud</name></author><id>tag:thibaud.space,2015-10-14:/2015/10/exporting-matlab-figures-to-latex.html</id><summary type="html">&lt;p&gt;The standard way of exporting figures from &lt;span class="caps"&gt;MATLAB&lt;/span&gt; to LaTeX is to use the builtin &lt;span class="docutils literal"&gt;print&lt;/span&gt; command to export the figure to an eps file using:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;depsc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;$&lt;span class="n"&gt;filename&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Unfortunately figures exported this way generally do not look like the original &lt;span class="caps"&gt;MATLAB&lt;/span&gt; figures. The aspect ratio is different and the fonts are too small. This can be overcome by tweaking the paper size, the font size and the line thickness but the process is fiddly and resulting eps files are hard to use since alternative TeX compilers like XeTeX and LuaTeX do not support eps files out of the box.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;The standard way of exporting figures from &lt;span class="caps"&gt;MATLAB&lt;/span&gt; to LaTeX is to use the builtin &lt;span class="docutils literal"&gt;print&lt;/span&gt; command to export the figure to an eps file&amp;nbsp;using:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;depsc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;$&lt;span class="n"&gt;filename&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Unfortunately figures exported this way generally do not look like the original &lt;span class="caps"&gt;MATLAB&lt;/span&gt; figures. The aspect ratio is different and the fonts are too small. This can be overcome by tweaking the paper size, the font size and the line thickness but the process is fiddly and resulting eps files are hard to use since alternative TeX compilers like XeTeX and LuaTeX do not support eps files out of the&amp;nbsp;box.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;
&lt;p&gt;It is possible to save &lt;span class="caps"&gt;MATLAB&lt;/span&gt; figures as pdf files using &lt;span class="docutils literal"&gt;print &lt;span class="pre"&gt;-dpdf&lt;/span&gt; $filename&lt;/span&gt;. This allows you to use XeLaTeX and LuaLaTeX but the other issues are not&amp;nbsp;solved.&lt;/p&gt;
&lt;p&gt;&lt;span class="caps"&gt;MATLAB&lt;/span&gt; can also save figures as png files. The resulting files actually look like the figures they were saved from but this is not recommended for publication quality graphics as high resolution images are often larger than their pdf/eps&amp;nbsp;equivalents.&lt;/p&gt;
&lt;p&gt;Enter &lt;a class="reference external" href="https://www.mathworks.com/matlabcentral/fileexchange/22022-matlab2tikz-matlab2tikz"&gt;matlab2tikz&lt;/a&gt;. This package translates MATlab figures into &lt;span class="docutils literal"&gt;tikz&lt;/span&gt; graphics using &lt;span class="docutils literal"&gt;pgfplots&lt;/span&gt; for direct inclusion into LaTeX documents. &lt;span class="docutils literal"&gt;tikz&lt;/span&gt; is a TeX package used to generate graphics whilst &lt;span class="docutils literal"&gt;pgfplots&lt;/span&gt; is a LaTeX package that provides convenient primitives for drawing plots. The resulting files are easy to use and modify. Additionally since they are just LaTeX code they are typeset with the rest of document and use the same fonts and font sizes. This makes them fit in a lot better and makes changing the document font or style easy since it doesn&amp;#8217;t require you to also edit your figures to change the font ! As an added bonus this also allows us to change the size and aspect ratio of our plots without having to regenerate them&amp;nbsp;!&lt;/p&gt;
&lt;section id="basic-usage"&gt;
&lt;h2&gt;Basic&amp;nbsp;Usage&lt;/h2&gt;
&lt;p&gt;To use &lt;span class="docutils literal"&gt;matlab2tikz&lt;/span&gt; you need to download the package and place it on you &lt;span class="caps"&gt;MATLAB&lt;/span&gt; path. Once &lt;span class="caps"&gt;MATLAB&lt;/span&gt; can find it saving your figures is as simple as calling &lt;span class="docutils literal"&gt;matlab2tikz(filename)&lt;/span&gt;. This will save the current figure. Your LaTeX distribution should be smart enough to do that for you once you&amp;#8217;ve added the proper &lt;span class="docutils literal"&gt;\usepackage&lt;/span&gt; directives to your&amp;nbsp;preamble.&lt;/p&gt;
&lt;p&gt;To add the figure to your LaTeX document you need to add the &lt;span class="docutils literal"&gt;pgfplots&lt;/span&gt; package to the preamble of your LaTeX&amp;nbsp;document:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;\usepackage&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;pgfplots&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\pgfplotsset&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;compat=newest&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\pgfplotsset&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;plot coordinates/math parser=false&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To add the figure to the LaTeX document simply insert it using &lt;span class="docutils literal"&gt;\input{}&lt;/span&gt; into a &lt;span class="docutils literal"&gt;figure&lt;/span&gt;&amp;nbsp;environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;figure&lt;span class="nb"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;\centering&lt;/span&gt;

        &lt;span class="k"&gt;\input&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;filename&lt;span class="nb"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;\caption&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;My cool figure&lt;span class="nb"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;\label&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;fig:my&lt;span class="nb"&gt;_&lt;/span&gt;figure&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\end&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;figure&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If your documents contains more than a couple &lt;span class="docutils literal"&gt;tikz&lt;/span&gt; graphics you will probably encounter the following&amp;nbsp;error:&lt;/p&gt;
&lt;pre class="literal-block"&gt;! TeX capacity exceeded, sorry [main memory size=3000000].&lt;/pre&gt;
&lt;p&gt;There are several work arounds to this problem. The simplest is to reduce the size of the generated graphics. When &lt;span class="docutils literal"&gt;matlab2tikz&lt;/span&gt; exports your graphs it saves every single point in the dataset even if they are so close that only a fraction of them will actually be visible. The &lt;span class="docutils literal"&gt;cleanfigure&lt;/span&gt; command provided with &lt;span class="docutils literal"&gt;matlab2tikz&lt;/span&gt; will reduce the number of datapoints  uused to draw the graph to the bare minimum required. Sometimes however it needs help: the &lt;span class="docutils literal"&gt;minimumPointsDistance&lt;/span&gt; option tells it how far away two points must be and discards all the points in between. Obviously, this value depends on your dataset. If correctly used you can significantly reduce your file&amp;nbsp;size.&lt;/p&gt;
&lt;p&gt;If this isn&amp;#8217;t enough, the &lt;a class="reference external" href="https://github.com/matlab2tikz/matlab2tikz/wiki/TeX-capacity-exceeded,-sorry"&gt;matlab2tikz wiki&lt;/a&gt; has some other&amp;nbsp;options.&lt;/p&gt;
&lt;p&gt;To export a figure simply&amp;nbsp;run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cleanfigure&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;matlab2tikz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;tikz/filename.tikz&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="advanced-usage"&gt;
&lt;h2&gt;Advanced&amp;nbsp;Usage&lt;/h2&gt;
&lt;section id="editing-your-figures"&gt;
&lt;h3&gt;Editing your&amp;nbsp;figures&lt;/h3&gt;
&lt;p&gt;The biggest advantage of this approach over the pdf/png options is that modifying your graphs after the fact is easy. Getting the exact look you want in &lt;span class="caps"&gt;MATLAB&lt;/span&gt; can be an exercise in frustration. &lt;span class="docutils literal"&gt;pgfplots&lt;/span&gt; by contrast is a pleasure to work with. Most of the syntax is self explanatory and the documentation is well&amp;nbsp;written.&lt;/p&gt;
&lt;p&gt;In one of my figures for example I needed to move the legend down because it was covering the axis labels. I just had to change the&amp;nbsp;line&lt;/p&gt;
&lt;pre class="literal-block"&gt;legend style={at={(0.5,-0.03)},anchor=north}&lt;/pre&gt;
&lt;p&gt;to&lt;/p&gt;
&lt;pre class="literal-block"&gt;legend style={at={(0.5,-0.2)},anchor=north}&lt;/pre&gt;
&lt;p&gt;Replacements like this can even be automated using &lt;span class="caps"&gt;MATLAB&lt;/span&gt; with very little code, just open the file, find the string you want to replace and close it&amp;nbsp;again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;% fix the positioning of the legend in the tikz file:&lt;/span&gt;
&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;fileread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;tikz/filename.tikz&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;new_src&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;strrep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;legend style={at={(0.5,-0.03)},anchor=north}&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;legend style={at={(0.5,-0.2)},anchor=north}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="k"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;fid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;tikz/filename.tikz&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new_src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;fclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section id="changing-the-aspect-ratio-of-a-figure"&gt;
&lt;h3&gt;Changing the aspect ratio of a&amp;nbsp;figure&lt;/h3&gt;
&lt;p&gt;If you export your figures as png setting the aspect ratio is as easy as changing the resolution of the generated image. Changing the aspect ratio is not as easy when export ing them as eps/pdf files. I often had to fiddle with &lt;span class="docutils literal"&gt;pbaspect&lt;/span&gt; and subplots to get the right result. With &lt;span class="docutils literal"&gt;matlab2tikz&lt;/span&gt; this is quite&amp;nbsp;easy.&lt;/p&gt;
&lt;p&gt;You can pass the desired height and width of your figure to the &lt;span class="docutils literal"&gt;matlab2tikz&lt;/span&gt; as LaTeX commands. By default the script tries to make reasonable assumptions about the width and height of your figures but this can lead to inconsistent results in some cases. If this is the case you can specify the &lt;span class="docutils literal"&gt;width&lt;/span&gt;&amp;nbsp;attribute:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;matlab2tikz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;filename&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;width&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;\textwidth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This also allows you to set the height of the figure as a function of the&amp;nbsp;width:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;matlab2tikz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;filename&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;width&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;\textwidth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;height&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;0.7\textwidth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Being so explicit about the figure width is bad practice. If we decide that we want smaller or bigger figures in our document we have to go back to the script that generates the figures and change the sizes manually. A better option is to create a special length just for  our figures. That way changing the size of the figures post-facto is as easy as redefining the length in our LaTeX document. To do this we need to add the following lines to the&amp;nbsp;preamble:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;\newlength\figureheight&lt;/span&gt;
&lt;span class="k"&gt;\newlength\figurewidth&lt;/span&gt;
&lt;span class="k"&gt;\setlength\figureheight&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;6cm&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\setlength\figurewidth&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;&lt;span class="k"&gt;\textwidth&lt;/span&gt;&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This defines the lengths &lt;span class="docutils literal"&gt;\figureheight&lt;/span&gt; and &lt;span class="docutils literal"&gt;\figurewidth&lt;/span&gt;. We just need to tell &lt;span class="docutils literal"&gt;matlab2tikz&lt;/span&gt; to use these instead of trying to&amp;nbsp;guess:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;matlab2tikz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;filename&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;width&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;\figurewidth&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;height&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;\figureheight&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These lengths can also be redefined for just one figure in our document. If for example we want on figure to be taller we just need to redefine the length in the figure&amp;nbsp;block:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;\begin&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;figure&lt;span class="nb"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;\setlength\figureheight&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;8cm&lt;span class="nb"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;\centering&lt;/span&gt;
        &lt;span class="k"&gt;\input&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;file.tikz&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\end&lt;/span&gt;&lt;span class="nb"&gt;{&lt;/span&gt;figure&lt;span class="nb"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section id="closing-notes"&gt;
&lt;h2&gt;Closing&amp;nbsp;Notes&lt;/h2&gt;
&lt;p&gt;Hopefully you are now able to produce nicer &lt;span class="caps"&gt;MATLAB&lt;/span&gt; figures with less work. If you find yourself repeating the same code often to export your figures consider wrapping your call to &lt;span class="docutils literal"&gt;matlab2tikz&lt;/span&gt; in a helper&amp;nbsp;function.&lt;/p&gt;
&lt;p&gt;You can find mine at &lt;a class="reference external" href="https://gist.github.com/comkieffer/48eff87e0d4f69fb7c49"&gt;github&lt;/a&gt;&lt;/p&gt;
&lt;/section&gt;
</content><category term="misc"/><category term="LaTeX"/><category term="matlab"/><category term="tikz"/><category term="pgfplots"/></entry></feed>