<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>emre şahin's digital garden 🍃 - Development</title>
    <link>https://emresahin.net/categories/development/</link>
    <description>Posts in the Development category</description>
    <language>en</language>
    <managingEditor>contact@emresahin.net (Emre Şahin)</managingEditor>
    <lastBuildDate>Tue, 15 Sep 2026 19:46:32 +0000</lastBuildDate>
    <atom:link href="https://emresahin.net/categories/development/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>devlog 27</title>
      <published>2025-04-24T03:17:59+00:00</published>
      <updated>2025-04-24T03:17:59+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 03:17:59 +0000</pubDate>
      <link>https://emresahin.net/devlog-27/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-27/</guid>
      <description>🐇 Tests are failing again: ghrl | get url | first https://github.com/iesahin/xvc/actions/runs/13875177382 🐢 We forgot to update the doc tests. Let’s run them again to update storage remove and file untrack commands. 🐇 There are issues with elision. ghpl ╭───┬──────────────────────┬───────────────...</description>
      <category>XVC</category>
      <category>Development</category>
      <category>trycmd</category>
      <category>doc tests</category>
      <category>GitHub Actions</category>
      <category>release checklist</category>
      <category>PyPI</category>
      <content:encoded><![CDATA[<p>🐇 Tests are failing again:</p>
<pre><code class="language-nu">ghrl | get url | first
https://github.com/iesahin/xvc/actions/runs/13875177382
</code></pre>
<p>🐢 We forgot to update the doc tests. Let’s run them again to update <code>storage remove</code> and <code>file untrack</code> commands.</p>
<p>🐇 There are issues with elision.</p>
<pre><code class="language-nu">ghpl

╭───┬──────────────────────┬────────────────────┬──────────────────────────────╮
│ # │     headRefName      │       title        │             url              │
├───┼──────────────────────┼────────────────────┼──────────────────────────────┤
│ 0 │ storage-remove-16674 │ xvc storage remove │ https://github.com/iesahin/x │
│   │                      │                    │ vc/pull/270                  │
╰───┴──────────────────────┴────────────────────┴──────────────────────────────╯
</code></pre>
<p>🐢 Tests are passing; we can merge the PR. But our commit hook to check the CHANGELOG doesn’t work. That’s weird; we don’t get any errors when the CHANGELOG is not in the push set.</p>
<pre><code class="language-nu">tmux new-window -c ($env.HOME | path join github.com iesahin xvc.py)  nvim 
</code></pre>
<p>🐢 Now we can update the Python bindings as well.</p>
<p>🐇 We forgot to bump package versions. We need to create a checklist for releases.</p>
<ul>
<li>✅ #🌻 CREATE a release checklist (2025-03-25 17:52)</li>
</ul>
<p>🐢 Let’s check if the latest version is updated on PyPI.</p>
<pre><code class="language-bash">pypi xvc
</code></pre>
<p>🐇 Yes, it is.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 26</title>
      <published>2025-04-24T03:01:04+00:00</published>
      <updated>2025-04-24T03:01:04+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 03:01:04 +0000</pubDate>
      <link>https://emresahin.net/devlog-26/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-26/</guid>
      <description>🐢 We can add type checking to xvc.py ’s command-line handler. Currently, it builds a command line manually from the given options and parses it with clap . It’s error-prone. 🐇 What are the options, though? xvc.py is just a wrapper around xvc , and that was the easiest way to get it working. We ca...</description>
      <category>XVC</category>
      <category>Development</category>
      <category>xvc.py</category>
      <category>clap</category>
      <category>command-line</category>
      <category>type-checking</category>
      <category>options</category>
      <content:encoded><![CDATA[<p>🐢 We can add type checking to <code>xvc.py</code>’s command-line handler. Currently, it builds a command line manually from the given options and parses it with <code>clap</code>. It’s error-prone.</p>
<p>🐇 What are the options, though? <code>xvc.py</code> is just a wrapper around <code>xvc</code>, and that was the easiest way to get it working. We can list all options manually in the headers as documentation, but it will be harder to maintain.</p>
<p>🐢 Once we start doing it, we’ll find a good way to simplify and shorten it.</p>
<p>🐇 Let’s start to work on <code>xvc file track</code>, then.</p>
<p>🐢 Worked on it a bit, and I decided it’s not worth it at the moment. We need to supply default values for most of the options. Maintaining a separate list of default values may not be feasible; it will be error-prone in a different way.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 25</title>
      <published>2025-04-24T02:57:26+00:00</published>
      <updated>2025-04-24T02:57:26+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 02:57:26 +0000</pubDate>
      <link>https://emresahin.net/devlog-25/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-25/</guid>
      <description>🐢 I want to update xvc.py to the latest version. 🐇 It should only be needed to update the dependency versions in Cargo.toml , right? 🐢 Let’s start with that. 🐇 We have interface changes regarding aliases; let’s start using uv for building. 🦊 Added requirements to pyproject.toml by running: uv add...</description>
      <category>XVC</category>
      <category>Python</category>
      <category>Development</category>
      <category>XVC</category>
      <category>Python</category>
      <category>uv</category>
      <category>Package Management</category>
      <category>Requirements</category>
      <content:encoded><![CDATA[<p>🐢 I want to update <code>xvc.py</code> to the latest version.</p>
<p>🐇 It should only be needed to update the dependency versions in <code>Cargo.toml</code>, right?</p>
<p>🐢 Let’s start with that.</p>
<p>🐇 We have interface changes regarding aliases; let’s start using <code>uv</code> for building.</p>
<p>🦊 Added requirements to <code>pyproject.toml</code> by running:</p>
<pre><code class="language-bash">uv add -r requirements.txt
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>devlog 24</title>
      <published>2025-04-24T02:54:54+00:00</published>
      <updated>2025-04-24T02:54:54+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 24 Apr 2025 02:54:54 +0000</pubDate>
      <link>https://emresahin.net/devlog-24/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-24/</guid>
      <description>🐇 Is there a way to implement Default for CLI structs? 🐢 There is a way if we start from the configuration, not just files. The default configuration is a TOML document. We can make it an XvcConfiguration struct and load and store it with confy . 🐇 We have a cascading set of configurations, but m...</description>
      <category>XVC</category>
      <category>Development</category>
      <category>XVC</category>
      <category>Rust</category>
      <category>Configuration</category>
      <category>confy</category>
      <category>CLI</category>
      <content:encoded><![CDATA[<p>🐇 Is there a way to implement <code>Default</code> for CLI structs?</p>
<p>🐢 There is a way if we start from the configuration, not just files. The default
configuration is a TOML document. We can make it an <code>XvcConfiguration</code> struct and
load and store it with <code>confy</code>.</p>
<p>🐇 We have a cascading set of configurations, but maybe we can start from the
struct and serialize/deserialize it on demand.</p>
<p>🐲 I don’t think we need to update <code>xvc-config</code> at the moment. It’s working, and
we don’t need to alter its inner workings in the near future.</p>
<p>🐢 I agree. We can use the <a href="https://docs.rs/config/"><code>config</code></a> crate when we need to update and have
enough time to work on this.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 23</title>
      <published>2025-02-03T10:30:44+00:00</published>
      <updated>2025-02-03T10:30:44+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 03 Feb 2025 10:30:44 +0000</pubDate>
      <link>https://emresahin.net/devlog-23/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-23/</guid>
      <description>🐇 Let’s turn to discussing the JSON output changes. We can add another option to XvcOutputLine , like XvcOutputLine::Json(T: Serialize) , that will output the type using serde_json . It won’t introduce any other type. 🐢 Yes, but what will T be? Although store structures have Serde implementations...</description>
      <category>XVC</category>
      <category>Development</category>
      <category>XVC</category>
      <category>Rust</category>
      <category>Serde</category>
      <category>JSON</category>
      <category>Serialization</category>
      <category>xvc file list</category>
      <content:encoded><![CDATA[<p>🐇 Let’s turn to discussing the JSON output changes. We can add another option
to <code>XvcOutputLine</code>, like <code>XvcOutputLine::Json(T: Serialize)</code>, that will output
the type using <code>serde_json</code>. It won’t introduce any other type.</p>
<p>🐢 Yes, but what will <code>T</code> be? Although store structures have Serde
implementations, they are not particularly useful for this.</p>
<p>🐇 We can have output types, named like <code>XvcFileListOutput</code>, that will be
converted to strings with serialization.</p>
<p>🦊 We do something similar in <code>xvc pipeline export</code> and <code>import</code> commands. We
use
<a href="https://github.com/iesahin/xvc/blob/main/pipeline/src/pipeline/schema.rs#L41"><code>XvcPipelineSchema</code></a>
and <code>XvcStepSchema</code> just for the import and export commands. We’ll write similar
structs for all JSON output and will use Serde to convert these to strings.</p>
<p>🐢 Unlike the <code>import</code> and <code>export</code> commands, we have optional fields in the
output, though. I don’t want content digests to appear in JSON output if they
are not required.</p>
<p>🐇 Let’s search for optional fields in Serde.</p>
<p>🦊 There is a <a href="https://docs.rs/optional-field/latest/optional_field/attr.serde_optional_fields.html">crate for optional
fields</a>.</p>
<p>🐢 We don’t need another crate for this. Serde has the
<a href="https://serde.rs/attr-skip-serializing.html"><code>skip_serializing_if</code></a> attribute
for fields. We can add <code>Option::is_none</code> as a method to these to skip outputting <code>None</code>
fields. All those fields, in this case, will be optional.</p>
<p>🐇 This is fine. We already use structs to format the <code>xvc file list</code> output. We
can just use them to output JSON.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 22</title>
      <published>2025-02-03T10:24:41+00:00</published>
      <updated>2025-02-03T10:24:41+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 03 Feb 2025 10:24:41 +0000</pubDate>
      <link>https://emresahin.net/devlog-22/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-22/</guid>
      <description>🐢 We need to find a theme for the blog. The current one breaks Nushell output tables because the code blocks are too narrow. 🐇 https://www.getzola.org/themes/pico/ is an option, but I don’t like its header. 🦊 Minimal Dark from the same author looks better: https://kuznetsov17.github.io/minimal-da...</description>
      <category>Digital Garden</category>
      <category>Development</category>
      <category>Theme</category>
      <category>Zola</category>
      <category>Anemone</category>
      <category>mdBook</category>
      <category>Nushell</category>
      <category>Netlify</category>
      <category>Syntax Highlighting</category>
      <category>Static Site Generator</category>
      <content:encoded><![CDATA[<p>🐢 We need to find a theme for the blog. The <a href="https://www.getzola.org/themes/anemone/">current
one</a> breaks Nushell output tables
because the code blocks are too narrow.</p>
<p>🐇 https://www.getzola.org/themes/pico/ is an option, but I don’t like its header.</p>
<p>🦊 Minimal Dark from the same author looks better: https://kuznetsov17.github.io/minimal-dark/notes/note1/</p>
<p>🐇 https://www.getzola.org/themes/no-style-please/ is also an option.</p>
<p>🦊 https://halve-z.netlify.app/posts/information/ looks interesting, but there is too much screen estate for the left bar.</p>
<p>🐢 Let’s start by running the site locally first.</p>
<p>🐇 There are errors in the configuration. That’s weird, but let’s fix these.</p>
<p>🐢 Fixed errors. These are probably related to a newer version. Can we take a look at <code>netlify.toml</code> to see if it downloads the same version?</p>
<p>🐇 There are breaking changes in Zola 0.19. Let’s update and push the Netlify config to see the results.</p>
<p>🐢 We need to add language support for Nushell to prevent warnings. Take a look at how to add a syntax file to Zola.</p>
<p>🐇 I added</p>
<pre><code class="language-toml">extra_syntaxes_and_themes = ["syntaxes"]
</code></pre>
<p>to the <code>[markdown]</code> section and added a <code>syntaxes/nushell.sublime-syntax</code> file copied from https://github.com/kurokirasama/nushell_sublime_syntax. I’m getting:</p>
<pre><code>Error: Reason: Error while compiling regex '\b(?x: 7z | ?
...
s-to-gdrive | usage | ver | verify | weather | wget-all | which-cd | wifi-info | wifi-pass | xls2csv | ydx | yt-api | ytcli | ytm | z | zi)\b'
Oniguruma error: target of repeat operator is not specified
</code></pre>
<p>🐢 The syntax highlighter may be a bit buggy. Let’s try to fix this if it’s a one-off.</p>
<p>🐇 Found the bug. There is a <code>?</code> in the regex that causes it to fail. Now it compiles, and Nushell blocks are colored.</p>
<p>🐢 Cool. Let’s fix the other warnings now. <code>shell</code> and <code>console</code> are not recognized, it looks like.</p>
<p>🐇 There is only Bash listed in https://www.getzola.org/documentation/content/syntax-highlighting/.</p>
<p>🦁 What do you think about migrating to mdBook? We already maintain mdBook for XVC; what about just moving the site to mdBook?</p>
<p>🐢 I thought about this before, and the only downside is the lack of an RSS feed.</p>
<p>🦊 I found this: https://github.com/theowenyoung/mdbook-rss</p>
<p>🐢 Now, this changes everything. We can even move the <code>nedriy.at</code> site to mdBook in this case.</p>
<p>🐇 Then, let’s start working on this. The site will be a technical book site in this case.</p>
<p>🐢 Does mdBook support Nushell syntax?</p>
<p>🐇 Nushell is not in the <a href="https://rust-lang.github.io/mdBook/format/theme/syntax-highlighting.html">listed languages</a>. mdBook uses <a href="https://highlightjs.org/">highlight.js</a>, and in its <a href="https://highlightjs.readthedocs.io/en/latest/supported-languages.html">listed languages</a>, we don’t find Nu either.</p>
<p>🐢 We already added Nu support to Zola, and we can just change the theme. This is a blocker in my opinion.</p>
<p>🐇 I searched for Nushell highlight.js support, and nothing appears. I think we can just postpone until Nu has more support on this front.</p>
<p>🐢 Yes. Let’s first try this change in the non-technical blog, and we can come back to this issue. Now, we’ll update <code>console</code> and <code>shell</code> to <code>bash</code>, I think.</p>
<p>🐇 Replaced <code>shell</code> and <code>console</code> with <code>bash</code>.</p>
<p>🐢 There is a file for <code>ggplot</code> that has warnings from earlier incarnations. We also lack syntax highlighters for Vim and Tmux.</p>
<p>🐇 There is a <code>sublime-syntax</code> file for Tmux at https://raw.githubusercontent.com/gerardroche/sublime-tmux/refs/heads/master/Tmux.sublime-syntax, but do we need it for a single file?</p>
<p>🐢 Let’s set it to plain text.</p>
<p>🐇 Now we only have Mermaid warnings left.</p>
<p>🐢 There should be a diagram at https://emresahin.net/developing-a-gitignore-crate/, but it doesn’t show up. We need a shortcode to show these, like the YouTube shortcode. Now let’s get back to theme selection.</p>
<p>🐇 I tested Karzok, but it doesn’t have category and tags support.</p>
<p>🐢 And I tested https://github.com/micahkepe/radion, but the best so far is the <code>apollo</code> theme. I’m struggling to modify the index page, though. I forgot that I modified the theme’s <code>index.html</code> file. I have content in <code>/content/_index.md</code> and a modified <code>index.html</code> in <code>/themes/anemone/templates/index.html</code> to show the content, tags, categories, etc. It should be fixed now.</p>
<p>🐇 Ah, cool. Can we clean the recent duplicate pages now?</p>
<p>🐢 Yeah, let’s take a look.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 21</title>
      <published>2025-02-03T09:59:28+00:00</published>
      <updated>2025-02-03T09:59:28+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 03 Feb 2025 09:59:28 +0000</pubDate>
      <link>https://emresahin.net/devlog-21/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-21/</guid>
      <description>🐢 Now, the next version will have a --json output for xvc file list . We can start working on it or update the Readme file? 🐇 What about adding at least command completions for Nushell? 🐢 Let’s read a bit about clap_complete_nushell . 🦊 There seems to be a nu-complete command. Let’s check its doc...</description>
      <category>xvc</category>
      <category>Development</category>
      <category>Nushell</category>
      <category>clap_complete_nushell</category>
      <category>carapace</category>
      <category>dynamic completions</category>
      <category>JSON</category>
      <category>Lazygit</category>
      <category>Rust</category>
      <category>XVC</category>
      <category>completions</category>
      <content:encoded><![CDATA[<p>🐢 Now, the next version will have a <code>--json</code> output for <code>xvc file list</code>. We
can start working on it or update the Readme file?</p>
<p>🐇 What about adding at least command completions for Nushell?</p>
<p>🐢 Let’s read a bit about <code>clap_complete_nushell</code>.</p>
<p>🦊 There seems to be a <code>nu-complete</code> command. Let’s check its documentation.</p>
<p>🐇 Nothing was found, and Kagi doesn’t help much either.</p>
<p>🐢 There is a completions document for Nushell: https://www.nushell.sh/book/custom_completions.html</p>
<p>🐇 There is a tool called carapace to provide completions across shells.</p>
<p>🐢 Its <a href="https://carapace-sh.github.io/carapace/carapace.html">documentation</a> is
thin, and I’m not sure if it supports dynamic completions out of the box. I
believe instead of adding a carapace setup, we can just write a Nushell
completion script that will use JSON output from the commands and add some
(maybe hidden) utility commands to support it.</p>
<p>🐇 There are a set of example scripts in the Nushell repo:
https://github.com/nushell/nu_scripts/tree/main/custom-completions</p>
<p>🐢 The reason I want to write custom completions for Nushell is that it will be
an exercise for the scripting language. <a href="https://github.com/nushell/nu_scripts/blob/main/custom-completions/gh/gh-completions.nu"><code>gh</code>
completions</a>
are not as scary as a Bash script.</p>
<p>🐇 <a href="https://github.com/nushell/nu_scripts/blob/main/custom-completions/git/git-completions.nu"><code>git</code>
completions</a>
are a better example for XVC. They simply run <code>git</code> whenever necessary. We can
start from a static completions command and update this with dynamic
completions manually. It will teach a lot.</p>
<p>🐢 I <a href="https://github.com/iesahin/nu_scripts">forked</a> the <code>nu_scripts</code> repo and
will add XVC completions script there.</p>
<p>🐇 Then let’s begin by adding Nushell static completions. Shall we add a
command for this?</p>
<p>🦊 Reviving the <code>completion</code> command we removed in 0.6.13?</p>
<p>🐢 We shouldn’t list it. We can make a <code>_comp</code> subcommand for the time being
and generate and distribute completions in the repository. When
<code>clap_complete_nushell</code> has the feature parity to provide dynamic completions,
we can remove these commands.</p>
<p>🐇 What will we use this for other than generating completions?</p>
<p>🐢 Maybe dynamic completions can call this as well.</p>
<p>🐇 Added Nushell static completions to be output using <code>xvc _comp generate-nushell</code>. Let’s bump up the version to 0.6.15.</p>
<pre><code>cargo set-version 0.6.15-alpha.1
   Upgrading xvc from 0.6.14 to 0.6.15-alpha.1
...
</code></pre>
<p>🐢 I noticed we forgot a line in the CLI command handler that asserts <code>xvc_root_opt.is_some()</code>, and this fails when we run <code>xvc</code> outside of repositories. We need to release this version quickly.</p>
<p>🐇 Oops, now, ok, let’s write a static Nushell generator and just release quickly.</p>
<p>🦊 Generating completions with</p>
<pre><code>xvc comp generate-nushell
</code></pre>
<p>🐢 Completion command is run with <code>comp</code> instead of <code>_comp</code>. Should we rename it?</p>
<p>🐇 Renamed it to <code>_comp</code>. It’s not hidden, but at least we can be sure that it won’t be misunderstood as a common command.</p>
<p>🐢 Bumping the version again. Now let’s source the generated script and test it.</p>
<pre><code>cargo set-version 0.6.15-alpha.2
   Upgrading xvc from 0.6.15-alpha.1 to 0.6.15-alpha.2
...
</code></pre>
<p>🦊 Yep, it works. We now have completions for Nushell.</p>
<p>🐢 Let’s update the completions documentation.</p>
<p>🐇 Done. Now, let’s take a look at CI and see what fails.</p>
<pre><code class="language-nu">ghrl | first
╭──────────────┬─────────────────────────────────────────────────────────╮
│ conclusion   │ success                                                 │
│ displayTitle │ Add Nushell completions                                 │
│ headBranch   │ nushell-completions                                     │
│ url          │ https://github.com/iesahin/xvc/actions/runs/13070200765 │
╰──────────────┴─────────────────────────────────────────────────────────╯
</code></pre>
<p>🐢 It fails because of coverage, not the tests. <a href="https://github.com/iesahin/xvc/pull/266#issuecomment-2626794240">Codecov says</a> the new code isn’t tested.</p>
<p>🐇 The added <code>xvc _comp</code> command isn’t tested. We can add a test running those lines and testing if the command outputs a completion script.</p>
<p>🐢 We have a <a href="https://github.com/iesahin/xvc/blob/main/lib/tests/test_completions.rs#L18">test for completions</a>. We can add a test that runs the lines.</p>
<p>🐇 Added a test and bumping up the version.</p>
<pre><code class="language-nu">cargo set-version 0.6.15-alpha.3
   Upgrading xvc from 0.6.15-alpha.2 to 0.6.15-alpha.3
...
</code></pre>
<p>🦊 We can add some more coverage while waiting for the tests.</p>
<p>🐇 <a href="https://app.codecov.io/gh/iesahin/xvc/blob/main/logging%2Fsrc%2Flib.rs#L285"><code>XvcOutputLine</code> implementation</a> seems to have no tests. It’s weird because we use these everywhere.</p>
<p>🐢 I’m not sure we use this particular implementation; we just use <code>XvcOutputLine::Info(s)</code>, not <code>XvcOutputLine::info(s)</code> anywhere. We can delete these methods actually.</p>
<p>🐇 We’ll add JSON output via this particular struct. Can we refactor these to use formatting for JSON, for example? Or use these to output JSON?</p>
<p>🦊 We can add a formatter to <code>XvcOutputLine</code> to output structures.</p>
<p>🐢 The enum is now defined as:</p>
<pre><code class="language-rust">#[derive(Clone, Debug)]
pub enum XvcOutputLine {
    /// The output that we should be reporting to user
    Output(String),
    /// For informational messages
    Info(String),
    /// For debug output to show the internals of Xvc
    Debug(String),
    /// Warnings that are against some usual workflows
    Warn(String),
    /// Errors that interrupts a workflow but may be recoverable
    Error(String),
    /// Panics that interrupts the workflow and ends the program
    /// Note that this doesn't call panic! automatically
    Panic(String),
    /// Progress bar ticks.
    /// Self::Info is also used for Tick(1)
    Tick(usize),
}</code></pre>
<p>Here, these fields can also have a <code>formatter</code> that will render the string in a particular format. For example, the output can be</p>
<pre><code class="language-rust">XvcOutputLine::Output(XvcJsonFormatter, String)</code></pre>
<p>🐇 I’m not sure this is a good idea. <code>Output</code> already specifies this string as output. We can have a wrapper instead, like,</p>
<pre><code class="language-rust">struct XvcJsonOutput(Format&lt;XvcStructuredOutput&gt;, XvcStructuredOutput)</code></pre>
<p>and we can use the supplied format to render <code>XvcStructuredOutput</code> to an output line with <code>XvcOutputLine::Output</code>. If we don’t provide output as structured, it will be too much error-prone work to convert the current outputs to structured.</p>
<p>🦊 The transition will also be gradual. We may not need structured output for most of the commands. We can start with <code>xvc file list</code> and convert others as we go.</p>
<p>🐢 This is sensible. By the way, coverage still didn’t increase. There may be something going on with Codecov or running the test.</p>
<pre><code class="language-nu">ghrl | first
╭──────────────┬─────────────────────────────────────────────────────────╮
│ conclusion   │ success                                                 │
│ displayTitle │ Add Nushell completions                                 │
│ headBranch   │ nushell-completions                                     │
│ url          │ https://github.com/iesahin/xvc/actions/runs/13087431038 │
╰──────────────┴─────────────────────────────────────────────────────────╯
</code></pre>
<p>🐇 Let’s run the test:</p>
<pre><code class="language-sh">cargo test -p xvc --test test_completions
...
test test_completions ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.80s
</code></pre>
<p>🐢 Can we make sure the output is a Nushell script and not an error message?</p>
<p>🐇 Let’s print it out.</p>
<p>🐢 It looks like when the <code>COMPLETE</code> environment variable is set, it never calls <code>_comp</code> subcommand and never calls those lines.</p>
<pre><code>cargo set-version 0.6.15-alpha.4
   Upgrading xvc from 0.6.15-alpha.3 to 0.6.15-alpha.4
...
</code></pre>
<p>🐢 Let’s make a release for 0.6.15. Coverage is OK now.</p>
<pre><code class="language-nu">cargo set-version 0.6.15
   Upgrading xvc from 0.6.15-alpha.4 to 0.6.15
...
</code></pre>
<pre><code class="language-nu">gh pr merge --squash --body $"(open CHANGELOG.md | lines | skip 2 | take 5)" --subject "Add static nushell completions"
</code></pre>
<p>🐇 Merged the PR.</p>
<p>🐢 Releases should appear in a few minutes.</p>
<p>🐇 We need to tag the merge commit for this.</p>
<p>🐢 Oh, yep. AFAIK Lazygit doesn’t have something for <code>git push --tags</code>. Let’s push from the CLI.</p>
<pre><code class="language-nu">git push --tags
You are on the main branch. Skipping CHANGELOG.md check.
To github.com:iesahin/xvc
 * [new tag]         v0.6.15 -&gt; v0.6.15
</code></pre>
<p>🦊 These commands, especially tables, are not rendered correctly on the web. We need to change the theme, I think.</p>]]></content:encoded>
    </item>
    <item>
      <title>More features for my Telegram bot</title>
      <published>2024-12-13T16:52:45+00:00</published>
      <updated>2024-12-13T16:52:45+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Fri, 13 Dec 2024 16:52:45 +0000</pubDate>
      <link>https://emresahin.net/more-features-for-my-telegram-bot/</link>
      <guid isPermaLink="true">https://emresahin.net/more-features-for-my-telegram-bot/</guid>
      <description>I have a Telegram bot to save files to my zettelkasten. It’s always there, waiting for my muses to visit. Currently, it only supports text. When I send a message, the bot adds it to a new file or appends it to a file modified within the last 30 minutes, which simplifies multi-paragraph inputs. I ...</description>
      <category>Automation</category>
      <category>Development</category>
      <category>Inboxbot</category>
      <category>Xvc</category>
      <category>Telegram</category>
      <category>Mime-types</category>
      <category>Zettelkasten</category>
      <category>Bots</category>
      <content:encoded><![CDATA[<p>I have a <a href="https://github.com/iesahin/inboxbot">Telegram bot</a> to save files to my zettelkasten. It’s always there, waiting for my muses to visit.</p>
<p>Currently, it only supports text. When I send a message, the bot adds it to a new file or appends it to a file modified within the last 30 minutes, which simplifies multi-paragraph inputs.</p>
<p>I need some more features to make it more useful.</p>
<p>I want it to save audio and image files to my inbox as well. I plan to run <a href="https://github.com/iesahin/xvc">Xvc</a> pipelines on these files to convert audio notes to text and make images searchable via OCR or tagging.</p>
<p>The first step to achieve this is to understand what kind of files Telegram can upload and download. I want to track these files with Xvc to avoid bloating the Git repository. I have a script that processes these files, but currently, it only commits changes to Git. I want to determine whether a file should be tracked by Xvc or Git.</p>
<p>I skimmed the <a href="https://core.telegram.org/bots/api#sending-files">Telegram API documentation</a> and found that it supports a wide range of file types. This led me to change my approach: I will track only <code>.txt</code> and <code>.md</code> files with Git and use Xvc for everything else.</p>
<p>In the future, I plan to add a <code>--binary-only</code> option to <code>xvc file track</code> to track binary files only. This will help me to track only non-text files when using globs as targets. I might also extend this to a general predicate to decide which files to track based on their size, modification time, or other properties.</p>]]></content:encoded>
    </item>
    <item>
      <title>Syncing Path Operations in Xvc</title>
      <published>2024-06-04T19:50:37+00:00</published>
      <updated>2024-06-04T19:50:37+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Tue, 04 Jun 2024 19:50:37 +0000</pubDate>
      <link>https://emresahin.net/syncing-path-operations-in-xvc/</link>
      <guid isPermaLink="true">https://emresahin.net/syncing-path-operations-in-xvc/</guid>
      <description>While writing a HOWTO post for the documentation, I found a bug where multiple carry-in commands were causing file system failures. When multiple threads were accessing the same cache directory, if one of them tried to set up the cache directory while another was still working on it, it caused a ...</description>
      <category>xvc</category>
      <category>Development</category>
      <category>Rust</category>
      <category>Concurrency</category>
      <category>File System</category>
      <category>Locking</category>
      <category>Path</category>
      <category>Multithreading</category>
      <category>Parallel</category>
      <category>OS</category>
      <content:encoded><![CDATA[<p>While writing a HOWTO post for the documentation, I found a bug where multiple
carry-in commands were causing file system failures. When multiple threads were
accessing the same cache directory, if one of them tried to set up the cache directory
while another was still working on it, it caused a permissions error.</p>
<p>Rust has <em>fearless concurrency</em> for memory access, but for the file system, there
seem to be no built-in locked access primitives.</p>
<p>I decided to write one. Parallel execution of file system operations is important
for Xvc. The error messages are annoying; having identical files in a repository is
common, and in those cases, these messages look like there is a problem. In
theory, when files are identical, having only one of them written to the cache
is not a problem, but there may be other issues preventing cache
access. We could just swallow the error and get away with it, but that’s not ideal.</p>
<p>Two options came to mind. One is modifying the list of cache files before
creating threads so that no two threads access the same cache file at the same
time. This is hard to implement and brings extra complexity to thread creation.
A one-in-a-thousand concern becomes an architectural burden.</p>
<p>The other solution is to lock paths while accessing them so two threads
working on the same cache path wait for each other. This is easier and requires
just dependency injection into the thread functions. It has the downside of
making file system operations slightly slower, as each path operation will now require
checking a mutex, but that seems of little concern for file system access, which
is already much slower than memory operations.</p>
<p>First, I tried to implement this with a <code>HashMap&lt;PathBuf, Mutex&lt;()&gt;&gt;</code>, but this
must also be passed to the function wrapped in <code>Arc&lt;Mutex&lt;HashMap&gt;&gt;</code>, which
made the “ceremony” of acquiring the lock for a single file much longer.</p>
<p>The issue is that you don’t want the <code>HashMap</code> itself to be a bottleneck. Multiple
threads shouldn’t wait for the <code>HashMap</code> to become available, as it’s not the
<code>HashMap</code> we want to lock, but the values inside it.</p>
<p>The solution is to return the lock value from a method of a struct. Something
like:</p>
<pre><code class="language-rust">pub struct PathSync {
    locks: Arc&lt;RwLock&lt;HashMap&lt;PathBuf, Arc&lt;Mutex&lt;()&gt;&gt;&gt;&gt;&gt;,
}</code></pre>
<p>Now the ceremony can be performed within the method, and threads working in
different directories won’t need to wait for the hash map to become available.</p>
<p>However, after implementing this, I realized I’d probably forget to lock a path
at some point. This is a general-purpose solution, and I should apply it to all
path operations when multiple threads are working. In most cases,
there are multiple paths to lock (cache_path, cache_dir, repository path), and if
I forget to lock one of them, a future user, some time, somewhere, will probably
see an error message.</p>
<p>So, I decided on a different approach: creating wrappers to run passed closures. This makes it
much more obvious that paths Xvc works on must be locked before operations.</p>
<pre><code class="language-rust">    pub fn with_sync_path(
        &amp;self,
        path: &amp;Path,
        mut f: impl FnMut(&amp;Path) -&gt; Result&lt;()&gt;,
    )</code></pre>
<p>This works by passing the path and a closure that operates on that path. It
first locks the path and then runs the closure. The locking mechanism allows
threads with different paths to run in parallel, but if they try to operate on the same path, they
will wait for each other.</p>
<p>The implementation is <a href="https://github.com/iesahin/xvc/blob/main/walker/src/sync.rs">here</a>.</p>]]></content:encoded>
    </item>
    <item>
      <title>devlog 3</title>
      <published>2024-06-04T10:07:01+00:00</published>
      <updated>2024-06-04T10:07:01+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Tue, 04 Jun 2024 10:07:01 +0000</pubDate>
      <link>https://emresahin.net/devlog-3/</link>
      <guid isPermaLink="true">https://emresahin.net/devlog-3/</guid>
      <description>Let’s begin this session by removing debug statements from both the Xvc library and the Python bindings. Another issue is the restart script. The if in that script that restarts the server doesn’t work; it always restarts the notebook server. I removed some println! statements from xvc.py . There...</description>
      <category>XVC</category>
      <category>Development</category>
      <category>Rust</category>
      <category>Python</category>
      <category>clippy</category>
      <category>Jupyter</category>
      <category>bug-fix</category>
      <category>notebook</category>
      <content:encoded><![CDATA[<p>Let’s begin this session by removing debug statements from both the Xvc library and the Python bindings.</p>
<p>Another issue is the restart script. The <code>if</code> in that script that restarts the server doesn’t work; it always restarts the notebook server.</p>
<p>I removed some <code>println!</code> statements from <code>xvc.py</code>. There doesn’t seem to be anything in the library related to outputs.</p>
<p>Let’s search for how to check if a <code>jupyter-lab</code> command with the port 7979 runs in the background.</p>
<p>It looks like the bug is in the condition; it’s not <code>-s</code>, it’s <code>-z</code>.</p>
<p>Oops, yeah.</p>
<p>Let’s do a bit of tidying and check if the script is fixed.</p>
<p>I have a <code>clippy</code> warning with a <code>new</code> function that says these usually don’t take <code>self</code> as a parameter. This is for the <code>pipeline new</code> command, and it receives a <code>self</code> as an <code>XvcPipeline</code> object. It seems best to turn off the <code>clippy</code> warning for this.</p>
<p>I allowed two <code>clippy</code> warnings, and the script seems to work fine.</p>
<p>Let’s go on to copying the content from the Xvc <code>README</code> to the notebook.</p>
<p>There is an issue with the <code>run-after-commit</code> script. When Xvc commits the changes, the command we give is run again.</p>
<p>The issue is that <code>git</code> initializes the directory in the <code>test-data/</code> directory, while <code>xvc</code> works in the current directory. I think we can either <code>git init</code> in the current directory or <code>xvc init</code> in <code>test-data</code>.</p>
<p>We’re already deleting <code>.git</code> and <code>.xvc</code> directories in the <code>start-readme</code> script. I think it may be easier to update <code>git init</code> to just initialize in the current directory.</p>
<p>Yep, let’s do it that way.</p>
<p>I see there are still extra outputs from the commands. We need to deal with this first.</p>
<p>I removed them, but there are still pink outputs. These are from <code>dbg!</code> statements, it looks like, or we’re initializing the output thread incorrectly.</p>
<p>Fixed those as well. I wrote up the <code>xvc file list</code> command examples as well. Now we have issues with <code>xvc storage new s3</code> not running, and not even showing any debug output. We’ll deal with it in the next devlog, though.</p>]]></content:encoded>
    </item>
    <item>
      <title>LMAX Disruptor</title>
      <published>2024-01-25T09:01:54+00:00</published>
      <updated>2024-01-25T09:01:54+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 25 Jan 2024 09:01:54 +0000</pubDate>
      <link>https://emresahin.net/lmax-disruptor/</link>
      <guid isPermaLink="true">https://emresahin.net/lmax-disruptor/</guid>
      <description>LMAX Disruptor The Disruptor is a concurrency framework created by LMAX to achieve very low latency and high throughput on their Java trading platform. LMAX found that using queues between system stages introduced latency, leading them to focus on optimizing this area. The Disruptor uses a lock-f...</description>
      <category>Development</category>
      <category>Summary</category>
      <category>Concurrency</category>
      <category>Summary</category>
      <category>Libraries</category>
      <category>Disruptor</category>
      <category>Concurrency</category>
      <category>Java</category>
      <category>Performance</category>
      <content:encoded><![CDATA[<p><a href="https://lmax-exchange.github.io/disruptor/">LMAX Disruptor</a></p>
<ul>
<li>The Disruptor is a concurrency framework created by LMAX to achieve very low latency and high throughput on their Java trading platform.</li>
<li>LMAX found that using queues between system stages introduced latency, leading them to focus on optimizing this area.</li>
<li>The Disruptor uses a lock-free “ring buffer” approach to avoid cache misses and locks at the CPU level, which are very costly.</li>
<li>The Disruptor is intended as a general-purpose solution for concurrent programming, not just for financial applications.</li>
<li>Applying the Disruptor pattern is not as simple as replacing all queues with the ring buffer; the user guide provides further guidance.</li>
<li>Various blogs, articles, technical papers, and performance tests explain the Disruptor’s inner workings.</li>
<li>A presentation and discussion group are also available for learning about the Disruptor from its creators.</li>
<li>Martin Fowler has reviewed the Disruptor’s use at LMAX.</li>
<li>The Disruptor is significantly faster than the ArrayBlockingQueue, as shown in the latency histogram.</li>
<li>More performance results are available comparing the Disruptor to other approaches.</li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>A Brief History of Xvc</title>
      <published>2024-01-22T09:17:15+00:00</published>
      <updated>2024-01-22T09:17:15+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 22 Jan 2024 09:17:15 +0000</pubDate>
      <link>https://emresahin.net/a-brief-history-of-xvc/</link>
      <guid isPermaLink="true">https://emresahin.net/a-brief-history-of-xvc/</guid>
      <description>In the first months of 2021, I decided to return to life after a long legal battle for divorce. Covid was still raging. I wasn’t keen to start a company or work in my country due to my half-deaf ears. I decided to find some open source projects and contribute, maybe get recognition, maybe hired. ...</description>
      <category>xvc</category>
      <category>development</category>
      <category>free software</category>
      <category>Software Engineering</category>
      <category>xvc</category>
      <category>history</category>
      <category>dvc</category>
      <category>Rust</category>
      <category>MLOps</category>
      <category>Open Source</category>
      <category>Iterative.ai</category>
      <category>Git Annex</category>
      <category>serde</category>
      <category>Blake3</category>
      <category>PyO3</category>
      <content:encoded><![CDATA[<p>In the first months of 2021, I decided to return to life after a long legal battle for divorce. Covid was still raging. I wasn’t keen to start a company or work in my country due to my half-deaf ears. I decided to find some open source projects and contribute, maybe get recognition, maybe hired.</p>
<p>I saw an ad on Stack Overflow Jobs those days about <em>employment by contributing to open source projects.</em> I applied to that. A few weeks later, the CTO of [iterative.ai] got in touch and I started working on DVC documentation. Initially on a per-hour basis, and after May 2021, as a full-time employee.</p>
<p>Initially, I liked the tool we were building very much. The team was awesome. (Still, they are.) It was one of the best periods of my life, especially in my turbulent still-ongoing-divorce-period pressures. I know I will always miss them.</p>
<p>My job was learning DVC, documenting it, and helping newcomers grasp it easily. It was a fun job. Until then, I didn’t see myself as a technical writer. English is not my native tongue and I never have lived in an English-speaking country. Nevertheless, I think I wasn’t <em>too bad</em> at it.</p>
<p>When I was first learning the tool, I began to use it everywhere. I was an avid user of Git Annex once. DVC looked better. I don’t remember why I lost interest in Git Annex after many years, but it was probably related to symbolic links not working on Windows (or on Termux). DVC had multiple ways of connecting the cache and the files in the workspace, including hardlinks and copy, so it was a breath of fresh air for me.</p>
<p>I began to use it for my large collections. Keeping track of my binary files in Git was something I always desired. Git is the <em>least sucking</em> version control system among the ones I used previously (SVN, hg, darcs…) and I’d rather keep using it everywhere rather than learning new tools for binary files.</p>
<p>After some time I began to use the tool for my personal file collections. I noticed its performance became a burden. I was tracking maybe a few gigabytes of files with it and basic file operations became slower as I added more. I noticed I was becoming distracted after I wrote a <code>dvc</code> command. It took some time to confess that the tool I liked once and was earning my salary with was not a tool that I liked to use.</p>
<p>I don’t know what <em>real professionals</em> would do at this point. I never had a good LinkedIn profile. When I met a similar problem with the example repository that’s supposed to contain 70,000 small files, I brought the issue forward. I wrote a shell script that was basically doing the same thing as <code>dvc add</code> and it worked much faster than the actual command. The shell script was naïve and I thought DVC must have <em>at least</em> that level of speed. It didn’t. Simply calling <code>md5sum</code> on files and copying them to appropriate location in <code>.dvc/cache</code> was way faster. How could this be?</p>
<p>I had cursory observations on the codebase. I know some decisions (like a large central class that connects everything, separate <code>.dvc</code> files for each tracked file) that may lead to degradation. Although I don’t see it as <em>the problem</em>, Python was also not helpful. These are rough observations.</p>
<p>It was September 2021. I was also teaching myself Rust. I wrote an email to the CTO and CEO of the company to request a sabbatical to work on DVC. My plan was to rewrite certain portions (or commands) in Rust and wrap them with PyO3. It could fail. So to have <em>skin in the game</em>, I said I’ll work for free during this time and if I fail to make DVC faster for some reason, I’ll return to my writing position.</p>
<p>They didn’t accept. I didn’t try to persuade them. The decision was rational and although I’d say <em>go ahead and see what happens</em> if I were in their shoes just to make my employee happy, they aren’t <em>crazy-managers</em> as I once was. Probably there are many factors that I’m not aware of. I returned to my post and continued to write documentation for another 9 months. In the meantime I studied Rust and thought about how I could architect a similar tool. Where does DVC go wrong?</p>
<p>In April 2022, I informed the CTO that I’d like to take a sabbatical for my book. I have a political-SF book and after the Ukrainian war started with a (albeit minor) probability of nuclear attack <em>on the other shore of Black Sea</em>, I thought it’s not a time to work on something I stopped liking. My performance in the last quarter was also not something I was proud of. I didn’t feel good.</p>
<p>When I retired to sabbatical in July though, while writing the book, I thought writing the software that I wanted to see was also <em>something on my mind before nuclear war.</em> I had notes about the architecture I was planning. I wanted to see if I could apply an Entity-Component System to this basic problem, without any Object-Oriented conceptions. I believe it looks cool. I’m still simplifying and testing the idea, and it looks better to my mind than mixing data and functions for no reason.</p>
<p>After I made the repository public, I resigned from Iterative.</p>
<p>In a sense, Xvc owes its existence to DVC, and the name is a tribute to this. I hope they squash their bugs, and improve their user experience, and be a long-term player in the crowded market they are in. I don’t intend to be a “competitor”, because I prefer being developer/architect rather than a “technical steward to VC money”, and the license of Xvc is GPL-3 to signal this.</p>
<p>The Xvc command line interface, however, is as different as it can be from DVC. The command names are different; DVC has commands similar to Git (<code>push</code>, <code>fetch</code>, <code>pull</code>, <code>commit</code>), while Xvc tries to be different from Git to reduce the user’s mental load. For example, as a writer, I noticed that “Git remotes” and “DVC remotes” was confusing, so I called them “Xvc storages”. DVC calls the units of a pipeline <em>stages</em>; the same concept is called <em>steps</em> in Xvc, because <em>stage</em> in Git is something completely different.</p>
<p>Internally, the architecture is also very different. Xvc uses serialization (with serde) instead of YAML. It can export/import pipelines from YAML (or JSON), but YAML is not as central as in DVC. (I believe YAML is overused in our industry, and it’s an employment guarantee for another generation of developers but there is better work than keeping up a half-baked configuration format.) Xvc doesn’t keep its artifacts in the user’s workspace (except <code>.xvcignore</code> files). They are all stored in the <code>.xvc/</code> directory. The DVC way of doing things makes merging <code>.dvc</code> files easier. To overcome the problems caused by merging large metadata files, Xvc keeps track of events and replays them to get the final state of the repository. All metadata storage and retrieval operations revolve around the <code>XvcStore&lt;T&gt;</code> struct in Xvc. Typically, if the user runs an <code>xvc</code> command, only the updated store events (added files, changed pipelines, etc.) are stored. There are optimizations in this front, but I profile first and optimize later.</p>
<p>Algorithms for data digests are configurable; by default Xvc uses Blake3, but it is configurable to use SHA2-256, SHA3-256, or Blake2s. It can be modified to use any 256-bit digest quickly. There are some features that are not found in DVC, and more will come. So, although I’m solving a similar problem, Xvc is not “DVC rewritten in Rust,” it’s a different tool completely.</p>
<p>Currently, it doesn’t have as much eye candy as DVC. In time, I plan to add Python, Julia, and R APIs, notebook integration, experiment tracking (without relying on Git internals), data labeling and filtering, and other MLOps features. I’m building with a goal to make these features available without making the rest of the software slower.</p>
<p>I’ve found the tool I was looking for to track my kids’ photos and Ottoman OCR datasets in a Git repository. I’m tracking more than 1TB of files in a single repository with Xvc and adding another 10TB looks feasible now.</p>]]></content:encoded>
    </item>
    <item>
      <title>bits 1</title>
      <published>2023-03-14T10:39:00+00:00</published>
      <updated>2023-03-14T10:39:00+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Tue, 14 Mar 2023 10:39:00 +0000</pubDate>
      <link>https://emresahin.net/bits-1/</link>
      <guid isPermaLink="true">https://emresahin.net/bits-1/</guid>
      <description>I began to explore the terrain for a Word plugin for the company I’m working at. Although there is a common format for the manifest file, Outlook and Word add-ins seem to require separate files. I haven’t seen any add-in in AppSource that serves as both a Word and an Excel add-in. Microsoft provi...</description>
      <category>bits</category>
      <category>development</category>
      <category>Microsoft Word</category>
      <category>add-in</category>
      <category>plugin</category>
      <category>API</category>
      <category>office</category>
      <category>javascript</category>
      <content:encoded><![CDATA[<p>I began to explore the terrain for a Word plugin for the company I’m working at.</p>
<ul>
<li>
<p>Although there is a common format for the <a href="https://learn.microsoft.com/en-us/office/dev/add-ins/develop/add-in-manifests?tabs=tabid-1">manifest</a> file, Outlook and Word add-ins seem to require separate files. I haven’t seen any add-in in <a href="https://appsource.microsoft.com/en-US/home?exp=ubp8">AppSource</a> that serves as both a Word and an Excel add-in.</p>
</li>
<li>
<p>Microsoft provides a tool called <a href="https://appsource.microsoft.com/en-us/product/office/WA104380862?corrid=94e8a3b6-7b45-7d61-7efa-2b5fb01abb83&amp;src=office&amp;exp=ubp8">Script Lab</a> that allows you to run arbitrary scripts and test APIs directly within Office.</p>
</li>
<li>
<p>For authentication, it is recommended to open a browser window to your web app using the <a href="https://learn.microsoft.com/en-us/office/dev/add-ins/develop/dialog-api-in-office-add-ins">Dialog API</a>.</p>
</li>
<li>
<p>It is usually not possible to use iframes to obtain authentication tokens from the task pane or sidebar itself. See <a href="https://stackoverflow.com/questions/67802639/outlook-web-addin-iframe-adfs-sites">this</a> answer for more details.</p>
</li>
<li>
<p>It is possible to add elements to the <a href="https://stackoverflow.com/questions/53844320/microsoft-word-add-in-add-to-contextual-menu">right-click menu</a> in Word for add-in commands.</p>
</li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>XVC State Machine</title>
      <published>2022-12-10T20:49:21+00:00</published>
      <updated>2022-12-10T20:49:21+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sat, 10 Dec 2022 20:49:21 +0000</pubDate>
      <link>https://emresahin.net/xvc-state-machine/</link>
      <guid isPermaLink="true">https://emresahin.net/xvc-state-machine/</guid>
      <description>I began writing Xvc’s pipeline and dependency handling. The best way to handle dependency states seems to be through a state machine. A state machine is a simple abstraction that shows state changes with respect to inputs. It can also have outputs for these state changes. There are some varieties...</description>
      <category>development</category>
      <category>xvc</category>
      <category>xvc</category>
      <category>rust</category>
      <category>finite state machines</category>
      <category>state machines</category>
      <category>pipeline</category>
      <category>programming</category>
      <content:encoded><![CDATA[<p>I began writing Xvc’s pipeline and dependency handling.
The best way to handle dependency states seems to be through a state machine.
A state machine is a simple abstraction that shows state changes with respect to inputs.
It can also have outputs for these state changes.
There are some varieties of this, but Xvc’s state machine (SM) is a simple one.</p>
<p>I first tried to use the <a href="https://crates.io/crates/rust-fsm"><code>rust-fsm</code></a> library, but it became apparent that Xvc pipeline steps’ states are tied to <code>XvcRoot</code>. That is, if we are to check the presence of a file or the value of a parameter, we have to do it relative to the repository root.
The root directory should be taken into consideration in every transition.</p>
<p>I checked the code and noticed that the FSM is actually very simple.
I copied it, added <code>&amp;XvcRoot</code> to the <code>transition</code> and <code>output</code> functions in the trait definition, and implemented it for <code>XvcOutput</code>, <code>XvcDependency</code>, and <code>XvcStep</code>.</p>
<p>These are the constituents of a pipeline.
A pipeline is composed of an <code>XvcStep</code> that defines a command, and each step can have multiple <code>XvcDependency</code> and <code>XvcOutput</code> definitions.
For each of these structs, I’ve added fields that represent their current state.</p>
<p>For example, an <code>XvcOutput</code> can be <code>Missing</code>, <code>Found</code>, <code>Old</code>, or <code>Ok</code>.
An <code>XvcStep</code> that produces this <code>XvcOutput</code> doesn’t check the dependency content hash if an output is missing.
However, if an <code>XvcOutput</code> is <code>Found</code>, the <code>XvcStepStateMachine</code> checks the <code>XvcDependency</code> states and their modification times to see if they have changed since the output was generated.
An <code>XvcStep</code> is invalidated when an <code>XvcOutput</code> is <code>Missing</code> or an <code>XvcDependency</code> has changed after the last command run.</p>
<p>Unlike DVC, I added the ability for an <code>XvcStep</code> to depend on other <code>XvcStep</code>s.
They communicate through outputs. I’ve added <code>XvcDependency::Step(XvcStep)</code> to the <code>XvcDependency</code> definition.
I’m also planning <code>XvcDependency::Pipeline(XvcPipeline)</code> to allow steps to depend on other pipelines, so that pipelines can be run in order.</p>
<p>Currently, the following are included as <code>XvcDependency</code>:</p>
<ul>
<li><code>File</code>: A (binary or text) file in the repository. If the metadata (size and modification time) or the content changes, the dependent step becomes invalidated.</li>
<li><code>Directory</code>: A directory that contains files. If a file is added to or removed from the directory, or any of the files are changed, the associated step becomes invalidated.</li>
<li><code>Glob</code>: A glob such as <code>my-data/*.png</code>. If the list of files changes or their content has changed, the associated step becomes invalidated.</li>
<li><code>Parameter</code>: Xvc can parse YAML, TOML, and JSON files and get the values of variables. It’s possible to define these (hyper)parameters as dependencies.</li>
<li><code>URL</code>: An HTTPS URL, which is checked first by metadata and then by content to see whether it has changed.</li>
<li><code>Step</code>: A previously defined step; if it’s invalidated, the depending step also becomes invalidated.</li>
</ul>
<p>Additionally, I’m planning to add the following items to Xvc as dependencies:</p>
<ul>
<li><code>Lines {path, begin, end}</code>: Lines in a text file. This can be used for general-purpose input tracking. If the given lines in a file are changed, the dependent step becomes invalidated.</li>
<li><code>Regex {path, regex}</code>: If the regular expression result on the file changes, the dependent stage becomes invalidated.</li>
<li><code>Pipeline { name }</code>: If any of the steps in a pipeline is invalidated, the pipeline is also invalidated, or the step that depends on this pipeline becomes invalidated.</li>
</ul>
<p>Each of these dependencies is checked minimally; that is, when their size on disk is detected to have changed, they are considered changed without checking the content hash. It needs a very detailed state machine to track the changes without bugs.</p>
<p>I’ve noticed that if I can write such a state machine, most of the I/O operations can be done in parallel. If two steps do not depend on each other in the dependency graph, they can be run in parallel. The state machine’s granularity allows this.</p>]]></content:encoded>
    </item>
    <item>
      <title>Xvc Devlog - 221109</title>
      <published>2022-11-10T09:26:00+00:00</published>
      <updated>2022-11-10T09:26:00+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 10 Nov 2022 09:26:00 +0000</pubDate>
      <link>https://emresahin.net/xvc-devlog---221109/</link>
      <guid isPermaLink="true">https://emresahin.net/xvc-devlog---221109/</guid>
      <description>🐇 How do you want to proceed from here, Mr. 🐢? 🐢 I think I can implement Rsync today. Looking at ssh2-rs , though, I think we can implement file transfer without relying on rsync . It might be easier to implement everything within the code. 🐇 Then you should rename the issue to new ssh . 🐢 Fair. ...</description>
      <category>devlog</category>
      <category>development</category>
      <category>xvc</category>
      <category>ssh</category>
      <category>rsync</category>
      <category>storage</category>
      <category>feature-flags</category>
      <category>rust</category>
      <category>libssh2</category>
      <content:encoded><![CDATA[<p>🐇 How do you want to proceed from here, Mr. 🐢?</p>
<p>🐢 I think I can implement Rsync today. Looking at <a href="https://docs.rs/ssh2/latest/ssh2/">ssh2-rs</a>, though, I think we can implement file transfer without relying on <code>rsync</code>. It might be easier to implement everything within the code.</p>
<p>🐇 Then you should rename the issue to <code>new ssh</code>.</p>
<p>🐢 Fair. There is also the <a href="https://docs.rs/ssh-rs/0.2.2/ssh_rs/">ssh_rs</a> crate, but it doesn’t have full support for the protocol. Instead, we can have another command, like <code>xvc storage new ssh</code>, that uses <code>libssh2</code> via the crate mentioned above. It has some limitations with OpenSSH on macOS.</p>
<p>🐇 From the <a href="https://github.com/alexcrichton/ssh2-rs">crate’s README</a>, it looks like you can enable the <code>vendored-openssl</code> feature to compile it statically.</p>
<p>🐢 Let’s go ahead then. It’s better to compile it behind a feature flag, though.</p>
<p>🐇 Yup. Rsync can be separate. I think for now you can implement rsync via <code>Exec::cmd</code> and make <code>new ssh</code> a new issue.</p>
<p>🐢 I’ll copy this conversation there.</p>
<hr>
<p>🐇 Now, let’s start implementing <code>rsync</code>.</p>
<p>🐢 Do we really want to hide it behind a feature flag? It doesn’t bring any extra complexity to <code>generic</code>, for example—just using the commands and returning the errors.</p>
<p>🐇 I think so. If the user doesn’t have <code>rsync</code> on their system, they’ll just get errors. We don’t need to make the implementation optional, but the tests might be.</p>
<p>🐢 OK.</p>]]></content:encoded>
    </item>
    <item>
      <title>Rust `ends_with` and `strip_prefix` behavior differences in `Path` and `str`</title>
      <published>2022-07-06T16:03:51+00:00</published>
      <updated>2022-07-06T16:03:51+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Wed, 06 Jul 2022 16:03:51 +0000</pubDate>
      <link>https://emresahin.net/Rust--ends_with--and--strip_prefix--behavior-differences-in--Path--and--str-/</link>
      <guid isPermaLink="true">https://emresahin.net/Rust--ends_with--and--strip_prefix--behavior-differences-in--Path--and--str-/</guid>
      <description>While writing an ignore library, I encountered subtle bugs caused by Rust’s Path behavior. ends_with in Path is different from ends_with in str : If you try to check whether a Path is a directory by its final character, you’ll find that path.ends_with("/") returns false . Path::strip_prefix also ...</description>
      <category>development</category>
      <category>rust</category>
      <category>programming</category>
      <category>rust</category>
      <category>Path</category>
      <category>str</category>
      <category>std</category>
      <category>debugging</category>
      <content:encoded><![CDATA[<p>While writing an ignore library, I encountered subtle bugs caused by Rust’s <code>Path</code> behavior.</p>
<ul>
<li>
<p><strong><code>ends_with</code> in <code>Path</code> is different from <code>ends_with</code> in <code>str</code></strong>:
If you try to check whether a <code>Path</code> is a directory by its final character, you’ll find that <code>path.ends_with("/")</code> returns <code>false</code>.</p>
</li>
<li>
<p><strong><code>Path::strip_prefix</code> also consumes the final slash</strong>:
If you have a directory marker at the end, such as <code>/Users/emre/mydir/</code>, and pass it to <code>strip_prefix("/Users/emre")</code>, you’ll get <code>mydir</code> instead of <code>mydir/</code>.</p>
</li>
</ul>]]></content:encoded>
    </item>
    <item>
      <title>Creating a file system watcher with ignore rules</title>
      <published>2022-07-06T15:55:49+00:00</published>
      <updated>2022-07-06T15:55:49+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Wed, 06 Jul 2022 15:55:49 +0000</pubDate>
      <link>https://emresahin.net/Creating-a-file-system-watcher-with-ignore-rules/</link>
      <guid isPermaLink="true">https://emresahin.net/Creating-a-file-system-watcher-with-ignore-rules/</guid>
      <description>In a previous post, I described the development of a file system walker. It returns files from a directory recursively while considering ignore patterns. In this post, I’ll update it. Some of the requirements have changed, and I need new features: walk_serial currently takes a Sender and uses cha...</description>
      <category>Development</category>
      <category>Rust</category>
      <category>rust</category>
      <category>ignore</category>
      <category>filesystem</category>
      <category>watcher</category>
      <category>notify</category>
      <content:encoded><![CDATA[<p>In a previous post, I described the development of a file system walker. It
returns files from a directory recursively while considering ignore patterns.</p>
<p>In this post, I’ll update it. Some of the requirements have changed, and I need
new features:</p>
<ul>
<li>
<p><code>walk_serial</code> currently takes a <code>Sender</code> and uses channels to send the results. This is
confusing, as it doesn’t use any parallelism to traverse the file system.</p>
</li>
<li>
<p>There are long-running processes that modify the file system, which can affect
behavior. We need to be notified about file system changes while using
identical ignore rules.</p>
</li>
</ul>
<p>For the first change, I’ll convert the signature of <code>walk_serial</code> to:</p>
<pre><code class="language-rust">pub fn walk_serial(
    given: IgnoreRules,
    dir: &amp;Path,
    walk_options: &amp;WalkOptions,
    res_paths: &amp;mut Vec&lt;Result&lt;PathMetadata&gt;&gt;,
) -&gt; Result&lt;IgnoreRules&gt;</code></pre>
<p>In the first version, instead of <code>res_paths</code>, we had a <code>sender</code> through which the
results were sent.</p>
<p>Why don’t I simply return the paths instead of receiving a <code>mut Vec</code> parameter to fill?
While it might be simpler, I don’t like the idea of creating a new <code>Vec</code> for
each function call. The overhead of creating new vectors and merging them can
become a burden for large directory trees. Instead, each call of the function
works on the same vector and expands its capacity when necessary. Since the
current <code>Vec</code> strategy is to double the capacity when needed, there
are fewer memory allocations for complex and large directory hierarchies.</p>
<p>Other than this, instead of <code>Sender::send</code>, the function uses <code>Vec::push</code> to
add elements to the vector. The rest of the implementation is similar.</p>
<p>One major difference in this version is that the function returns an <code>IgnoreRules</code> value instead of <code>()</code>.
This contains the compiled ignore rules from all directories below the given directory.
We can use this returned value to further check the paths we get from the notifier we’ll develop.</p>
<h2 id="adding-a-file-system-notifier">Adding a file system notifier</h2>
<p>For the second change, we need to access the underlying OS machinery for
file system notifications. My research led me to the <code>notify</code> crate. Although it
seems to be undergoing a major overhaul as of this writing, it’s the only promising
option other than interfacing directly with the OS.</p>
<p>Again, we begin by defining the enums and structs for this problem.</p>
<p>What do we want? We want to collect file system events to update the
<code>Vec&lt;PathMetadata&gt;</code> that was built with <code>walk_serial</code> or <code>walk_parallel</code>.
<code>PathMetadata</code> was defined as:</p>
<pre><code class="language-rust">pub struct PathMetadata {
    pub path: PathBuf,
    pub metadata: Metadata,
}</code></pre>
<p>We could build a <code>HashMap&lt;PathBuf, Metadata&gt;</code> or keep this as a vector, but for
our purposes, we want to track new events. These events correspond to creating a
new file, updating metadata (size, timestamp), or deleting a file. We don’t need, for example, permission changes, as we primarily want to track which file
is newer or whose size has changed.</p>
<pre><code class="language-rust">pub enum PathEvent {
    Create { path: PathBuf, metadata: Metadata },
    Update { path: PathBuf, metadata: Metadata },
    Delete { path: PathBuf },
}</code></pre>
<p><a href="https://github.com/notify-rs/notify"><code>notify</code></a> provides an abstraction over different operating systems.
It’s called <code>RecommendedWatcher</code> and is used to provide a consistent interface for events.
A watcher is built by implementing the <code>EventHandler</code> trait.</p>
<p>Since we need to check whether paths are ignored before reporting them, the watcher should receive an <code>IgnoreRules</code> struct.</p>
<pre><code class="language-rust">pub fn make_watcher(
    ignore_rules: IgnoreRules,
) -&gt; Result&lt;(RecommendedWatcher, Receiver&lt;PathEvent&gt;)&gt; {</code></pre>
<p>It returns a <code>RecommendedWatcher</code> and a (crossbeam) <code>Receiver</code> for the <code>PathEvent</code> we defined above.
The implementation creates a channel, initializes the watcher, and starts watching.</p>
<pre><code class="language-rust">    let (sender, receiver) = bounded(10000);
    let root = ignore_rules.root.clone();
    let mut watcher = notify::recommended_watcher(PathEventHandler {
        ignore_rules,
        sender,
    })?;
    watcher.watch(&amp;root, RecursiveMode::Recursive)?;</code></pre>
<p><code>PathEventHandler</code> is the struct that implements the <code>EventHandler</code> trait.
The implementation requires a single function to be implemented:</p>
<pre><code class="language-rust">    fn handle_event(&amp;mut self, event: notify::Result&lt;Event&gt;)</code></pre>
<p><a href="https://docs.rs/notify/5.0.0-pre.15/notify/event/struct.Event.html"><code>notify::Event</code></a> is a hierarchical set of enums that describe the type of file system event.
The event handler is expected to discriminate between these events and react accordingly.</p>
<pre><code class="language-rust">pub struct Event {
    pub kind: EventKind,
    pub paths: Vec&lt;PathBuf&gt;,
    pub attrs: EventAttributes,
}</code></pre>
<p>We’re interested in filtering events by <code>EventKind</code>, checking whether the paths are ignored, and sending the path and metadata through a channel.
We have three kinds of events in <code>PathEvent</code>.
<code>PathEventHandler</code> implements event filtering and calls handlers for these <code>PathEvent</code>s.</p>
<p>The struct has functions that correspond to one or more <code>PathEvent</code> elements.
These are <code>create_event</code>, <code>write_event</code>, <code>remove_event</code>, and <code>rename_event</code>.
The first three correspond directly to members of <code>PathEvent</code>, and the latter sends both a <code>PathEvent::Delete</code> and a <code>PathEvent::Create</code>.</p>
<pre><code class="language-rust">match event.kind {
    notify::EventKind::Create(_) =&gt; self.create_event(event.paths[0].clone()),
    notify::EventKind::Modify(mk) =&gt; match mk {
        notify::event::ModifyKind::Any =&gt; todo!(),
        notify::event::ModifyKind::Data(_) =&gt; self.write_event(event.paths[0].clone()),
        notify::event::ModifyKind::Metadata(_) =&gt; {
            self.write_event(event.paths[0].clone())
        }
        notify::event::ModifyKind::Name(rk) =&gt; match rk {
            notify::event::RenameMode::Any =&gt; {}
            notify::event::RenameMode::To =&gt; self.create_event(event.paths[0].clone()),
            notify::event::RenameMode::From =&gt; {
                self.remove_event(event.paths[0].clone())
            }
            notify::event::RenameMode::Both =&gt; {
                self.rename_event(event.paths[0].clone(), event.paths[1].clone())
            }
            notify::event::RenameMode::Other =&gt; {}
        },
        notify::event::ModifyKind::Other =&gt; {}
    },
    notify::EventKind::Remove(_) =&gt; self.remove_event(event.paths[0].clone()),
    notify::EventKind::Any =&gt; {}
    notify::EventKind::Access(_) =&gt; {}
    notify::EventKind::Other =&gt; {}
}</code></pre>
<p>An example event handler should clarify how the others work:</p>
<pre><code class="language-rust">fn write_event(&amp;mut self, path: PathBuf) {
    match check_ignore(&amp;self.ignore_rules, &amp;path) {
        MatchResult::Whitelist | MatchResult::NoMatch =&gt; {
            self.sender
                .send(PathEvent::Create {
                    path: path.clone(),
                    metadata: path.metadata().map_err(Error::from).unwrap(),
                })
                .unwrap_or_else(|e| warn!("{}", e));
        }
        MatchResult::Ignore =&gt; {
            debug!("FS Notification Ignored: {}", path.to_string_lossy());
        }
    }
}</code></pre>
<p>Other event handlers work similarly. They send <code>PathEvent</code> values that can be used to update a list of paths and their metadata.</p>
<p>To use this watcher, you first retrieve the list of files with <code>walk_serial</code>.
It returns a snapshot of the directory and the <code>IgnoreRules</code> collected from the directories below it.
Then, you create a <code>watcher</code> with:</p>
<pre><code class="language-rust">let ignore_rules = walk_serial(...)?;
let (watcher, path_event_rec) = make_watcher(ignore_rules)?;</code></pre>
<p>After this, you can create another thread to monitor file system changes:</p>
<pre><code class="language-rust">crossbeam::scope(|s| {
    s.spawn(|_| {
        while let Ok(path_event) = path_event_rec.recv() {
            match path_event {
                PathEvent::Create { .. } =&gt; { /* ... */ }
                PathEvent::Delete { .. } =&gt; { /* ... */ }
                PathEvent::Update { .. } =&gt; { /* ... */ }
            }
        }
    });
});</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Unit tests vs Integration Tests in Rust</title>
      <published>2022-06-11T05:55:58+00:00</published>
      <updated>2022-06-11T05:55:58+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Sat, 11 Jun 2022 05:55:58 +0000</pubDate>
      <link>https://emresahin.net/Unit-tests-vs-Integration-Tests-in-Rust/</link>
      <guid isPermaLink="true">https://emresahin.net/Unit-tests-vs-Integration-Tests-in-Rust/</guid>
      <description>One thing I’ve noticed since I started working with Rust is that Test-Driven Development (TDD) has become much more feasible. As a developer who is “absentminded by default,” I highly value tests. However, if the feedback cycle is slow, it becomes prohibitively expensive to test everything. This ...</description>
      <category>Rust</category>
      <category>Testing</category>
      <category>development</category>
      <category>unit tests</category>
      <category>integration tests</category>
      <category>Rust</category>
      <category>CI/CD</category>
      <category>TDD</category>
      <category>best practices</category>
      <content:encoded><![CDATA[<p>One thing I’ve noticed since I started working with Rust is that <em>Test-Driven Development</em> (TDD) has become much more feasible. As a developer who is “absentminded by default,” I highly value tests. However, if the feedback cycle is slow, it becomes prohibitively expensive to test everything. This is likely why unit tests are often favored over integration tests: they are more precise, faster to execute, and make rapid round-trip testing more achievable.</p>
<p>Another point is that Rust, being a statically typed language, requires far fewer unit tests than dynamic languages. Traditionally, unit tests are used to validate “input consumption” (ensuring input is processed correctly) and “well-formed output” (verifying that the output meets requirements under specific conditions). In Rust, many of these input-output concerns are resolved at compile time through the use of expressive types like structs and enums.</p>
<p>For these reasons, I find myself more inclined toward integration tests that reflect real-world requirements, rather than focusing solely on unit tests in artificial settings.</p>]]></content:encoded>
    </item>
    <item>
      <title>Death of Agile?</title>
      <published>2022-04-11T06:06:08+00:00</published>
      <updated>2022-04-11T06:06:08+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Mon, 11 Apr 2022 06:06:08 +0000</pubDate>
      <link>https://emresahin.net/Death-of-Agile-/</link>
      <guid isPermaLink="true">https://emresahin.net/Death-of-Agile-/</guid>
      <description>I recently watched a relatively old talk by Allen Holub: The talk is insightful. He argues that Scrum is not agile and that it imposes a very strict set of rules that have nothing to do with true agility. While I am convinced that Scrum isn’t agile , I’m not convinced that we don’t need processes...</description>
      <category>Development</category>
      <category>Management</category>
      <category>agile</category>
      <category>scrum</category>
      <category>motivation</category>
      <category>engineering management</category>
      <category>Allen Holub</category>
      <category>processes</category>
      <content:encoded><![CDATA[<p>I recently watched a relatively old talk by Allen Holub:</p>
<div class="video-embed"><iframe src="https://www.youtube.com/embed/vSnCeJEka_s" title="YouTube video" frameborder="0" allowfullscreen="" loading="lazy"></iframe></div>

<p>The talk is insightful. He argues that Scrum is <em>not</em> agile and that it imposes a very
strict set of rules that have nothing to do with true agility.</p>
<p>While I am convinced that <em>Scrum isn’t agile</em>, I’m not convinced that we
don’t need processes or habits at all. Another talk of his is this:</p>
<div class="video-embed"><iframe src="https://www.youtube.com/embed/F42A3R28WMU" title="YouTube video" frameborder="0" allowfullscreen="" loading="lazy"></iframe></div>

<p>Basically, what agile boils down to is self-managing teams composed of
self-managing people working in close collaboration with the customer. This
requires an endless supply of motivation for development. If we take <em>the
team is motivated</em> as a given, then it’s certain that hindrances from rigid processes
don’t make sense. For me, for Allen, and for many others, this may be true, but my
experience shows me that those who are really motivated to develop even
<em>for free</em> are in the minority. For most folks out there, development is a job
like any other. Their supply of motivation is limited, and when we make them
self-govern, they might not actually do the work; they might just read, doodle,
spend their budget on useless things, get bored, and jump to another job.</p>
<p>In his second talk, he says, <em>“We assume we’re grown-ups.”</em> I’d like to assume
that as well, and I certainly assume this for myself—I’d do development on my own time.
Most of my development in the last 25 years has been essentially for free, without an
explicit financial goal. However, when I’ve managed projects, I’ve seen that <em>real
life</em> doesn’t always support such an assumption. Software development is a lucrative
profession. If we start with mainly financial motivations, <em>self-governing</em>
becomes a bit of a dream. If I’ll earn the same amount whether I work today or not,
I might skip the work and engage in another joyful activity. In my case, that
activity is <em>another kind of development</em>, but does it matter?</p>
<p>This doesn’t mean Scrum is good and should be endorsed. I don’t like it and
haven’t used it much anyway. But the <em>ideals</em> Allen discusses can’t survive
on their own. They need some way to supply motivation, some direction, some
form of <em>soft</em> governing. Agile in its original sense looks like anarchy, but anarchy
often gives birth to some form of governance at some point. In the past, there were people
who had no hierarchical management, but when hierarchies were invented (or
introduced by others), they became powerful.</p>
<p>We can continue to believe that developing like aboriginal tribes, in a <em>people
over processes</em> fashion, leads to ultimate success. That may be true in certain
scenarios, but in general, those tribes can’t maintain coherence after a certain
size. Development teams, in my humble opinion, are similar.</p>]]></content:encoded>
    </item>
    <item>
      <title>Premature Caching is the Root of All Evil</title>
      <published>2021-12-22T20:32:35+00:00</published>
      <updated>2021-12-22T20:32:35+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Wed, 22 Dec 2021 20:32:35 +0000</pubDate>
      <link>https://emresahin.net/premature-caching/</link>
      <guid isPermaLink="true">https://emresahin.net/premature-caching/</guid>
      <description>I’m writing a Rust command line app in my spare time to learn the language. It involves some file system checks where I use fs::metadata . As everyone knows , accessing the disk is an expensive operation and must be kept to a minimum. I was thinking of using a HashMap::&lt;Path, Metadata&gt; to cache t...</description>
      <category>Development</category>
      <category>Software Engineering</category>
      <category>rust</category>
      <category>caching</category>
      <category>xvc</category>
      <category>optimization</category>
      <category>performance</category>
      <content:encoded><![CDATA[<p>I’m writing a Rust command line app in my spare time to learn the language. It
involves some file system checks where I use <code>fs::metadata</code>. As <em>everyone
knows</em>, accessing the disk is an <em>expensive</em> operation and must be kept to a
minimum. I was thinking of using a <code>HashMap::&lt;Path, Metadata&gt;</code> to cache the
results for paths.</p>
<p>I then came across the <a href="https://crates.io/cached">cached</a> crate. It caches the results of functions for
memoization. <em>This is exactly what I need</em>, I thought. Internally, it does what I
was planning to do.</p>
<p>Later, I noticed the possible bugs that could arise. I’m thinking of using the
function in a short-running process, so the metadata is not expected to change
during the run. For some reason, suppose the runtime of the process began to
get longer, or I decided to add a web server on top of it. At that time,
probably many moons from now, I’ll have forgotten the decision I made about
caches and my assumption that the metadata won’t change during the run. It
will cause some weird bugs when file timestamp changes aren’t detected.</p>
<p>No one will notice that I’m fixing bugs if they never appear, but I believe this
is the best kind of software engineering.</p>
<h2 id="commentary-2022-08-01">Commentary (2022-08-01)</h2>
<ul>
<li>It looks like my assumption that RAM is <em>significantly faster</em> than disk access may also be wrong.
SSDs are fast, and for parallel access, they may perform as fast as RAM.</li>
</ul>]]></content:encoded>
    </item>
  </channel>
</rss>
