<?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/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Posts about C#</title><link>https://chriswarrick.com/</link><atom:link href="https://chriswarrick.com/blog/tags/csharp.xml" rel="self" type="application/rss+xml" /><description>A rarely updated blog, mostly about programming.</description><lastBuildDate>Mon, 16 Feb 2026 21:15:00 GMT</lastBuildDate><generator>https://github.com/Kwpolska/YetAnotherBlogGenerator</generator><item><title>I Wrote YetAnotherBlogGenerator</title><dc:creator>Chris Warrick</dc:creator><link>https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/</link><pubDate>Mon, 16 Feb 2026 21:15:00 GMT</pubDate><guid>https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/</guid><description>Writing a static site generator is a developer rite of passage. For the past 13 years, this blog was generated using Nikola. This week, I finished implementing my own generator, the unoriginally named YetAnotherBlogGenerator.
Why would I do that? Why would I use C# for it? And how fast is it? Continue reading to find out.
</description><content:encoded><![CDATA[<p>Writing a static site generator is a developer rite of passage. For the past 13 years, this blog was generated using <a href="https://getnikola.com/">Nikola</a>. This week, I finished implementing my own generator, the unoriginally named <a href="https://github.com/Kwpolska/YetAnotherBlogGenerator">YetAnotherBlogGenerator</a>.</p>
<p>Why would I do that? Why would I use C# for it? And how fast is it? Continue reading to find out.</p>



<h2 id="ok-but-why">OK, but why?</h2>
<p>You might have noticed I’m not happy with <a href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/">the Python</a> <a href="https://chriswarrick.com/blog/2024/01/15/python-packaging-one-year-later">packaging ecosystem</a>. But the language itself is no longer fun for me to code in either. It is especially not fun to maintain projects in. <a href="https://discuss.python.org/t/revisiting-pep-505-none-aware-operators/74568/">Elementary quality-of-life features</a> get bogged down in months of discussions and design-by-committee. At the same time, there’s a new release every year, full of removed and deprecated features. A lot of churn, without much benefit. I just don’t feel like doing it anymore.</p>
<p>Python is praised for being fast to develop in. That’s certainly true, but a good high-level statically-typed language can yield similar development speed with more correctness from day one. For example, I coded an entire table-of-contents-sidebar feature in one evening (and one more evening of CSS wrangling to make it look good). This feature extracts headers from either the Markdown AST or the HTML fragment. I could do it in Python, but I’d need to jump through hoops to get Python-Markdown to output headings with IDs. In C#, introspecting what a class can do is easier thanks to great IDE support and much less dynamic magic happening at runtime. There are also decompiler tools that make it easy to look under the hood and see what a library is doing.</p>
<p>Writing a static site generator is also a learning experience. A competent SSG needs to ingest content in various formats (as nobody wants to write blog posts in HTML by hand) and generate HTML (usually from templates) and XML (which you could, in theory, do from templates, but since XML parsers are not at all lenient, you don’t want to). Image processing to generate thumbnails is needed too. And to generate correct RSS feeds, you need to parse HTML to rewrite links. The list of small-but-useful things goes on.</p>
<h2 id="is-c.net-a-viable-technology-stack-for-a-static-site-generator">Is C#/.NET a viable technology stack for a static site generator?</h2>
<p>C#/.NET is certainly not the most popular technology stack for static site generators. <a href="https://jamstack.org/generators/">JamStack.org</a> have gathered a list of 377 SSGs. <a href="https://chriswarrick.com/listings/yabg-intro/jamstack-org-generators.js.html">Grouping by language</a>, there are 154 generators written in JavaScript or TypeScript, 55 generators written in Python, and 28 written in <em>PHP</em> of all languages. C#/.NET is in sixth place with 13 (not including YABG; I’m probably not submitting it).</p>
<p>However, it is a pretty good choice. Language-level support for concurrency with <code>async</code>/<code>await</code> (based on a thread pool) and JIT compilation help to make things fast. But it is still a high-level, object-oriented language where you don’t need to manually manage memory (hi Rustaceans!).</p>
<p>The library ecosystem is solid too. There are plenty of good libraries for working with data serialization formats: <a href="https://joshclose.github.io/CsvHelper/">CsvHelper</a>, <a href="https://github.com/aaubry/YamlDotNet">YamlDotNet</a>, <a href="https://www.nuget.org/packages/Microsoft.Data.Sqlite/">Microsoft.Data.Sqlite</a>, and the built-in <a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/overview">System.Text.Json</a> and <a href="https://learn.microsoft.com/en-us/dotnet/standard/linq/linq-xml-overview">System.Xml.Linq</a>. <a href="https://github.com/xoofx/markdig">Markdig</a> handles turning Markdown into HTML. <a href="https://github.com/sebastienros/fluid">Fluid</a> is an excellent templating library that implements the Liquid templating language. <a href="https://html-agility-pack.net/">HtmlAgilityPack</a> is solid for manipulating HTML, and <a href="https://github.com/dlemstra/Magick.NET">Magick.NET</a> wraps the ImageMagick library.</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_40b21992ea7c1038c9310b71679b5d10743805d9-1"><code data-line-number=" 1"></code></a></td><td class="code"><code><a id="code_40b21992ea7c1038c9310b71679b5d10743805d9-1" name="code_40b21992ea7c1038c9310b71679b5d10743805d9-1"></a><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;CsvHelper&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;33.1.0&quot;</span><span class="nt">/&gt;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_40b21992ea7c1038c9310b71679b5d10743805d9-2"><code data-line-number=" 2"></code></a></td><td class="code"><code><a id="code_40b21992ea7c1038c9310b71679b5d10743805d9-2" name="code_40b21992ea7c1038c9310b71679b5d10743805d9-2"></a><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;Fluid.Core&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;2.31.0&quot;</span><span class="nt">/&gt;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_40b21992ea7c1038c9310b71679b5d10743805d9-3"><code data-line-number=" 3"></code></a></td><td class="code"><code><a id="code_40b21992ea7c1038c9310b71679b5d10743805d9-3" name="code_40b21992ea7c1038c9310b71679b5d10743805d9-3"></a><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;Fluid.ViewEngine&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;2.31.0&quot;</span><span class="nt">/&gt;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_40b21992ea7c1038c9310b71679b5d10743805d9-4"><code data-line-number=" 4"></code></a></td><td class="code"><code><a id="code_40b21992ea7c1038c9310b71679b5d10743805d9-4" name="code_40b21992ea7c1038c9310b71679b5d10743805d9-4"></a><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;HtmlAgilityPack&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;1.12.4&quot;</span><span class="nt">/&gt;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_40b21992ea7c1038c9310b71679b5d10743805d9-5"><code data-line-number=" 5"></code></a></td><td class="code"><code><a id="code_40b21992ea7c1038c9310b71679b5d10743805d9-5" name="code_40b21992ea7c1038c9310b71679b5d10743805d9-5"></a><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;Magick.NET-Q8-AnyCPU&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;14.10.2&quot;</span><span class="nt">/&gt;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_40b21992ea7c1038c9310b71679b5d10743805d9-6"><code data-line-number=" 6"></code></a></td><td class="code"><code><a id="code_40b21992ea7c1038c9310b71679b5d10743805d9-6" name="code_40b21992ea7c1038c9310b71679b5d10743805d9-6"></a><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;Markdig&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;0.45.0&quot;</span><span class="nt">/&gt;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_40b21992ea7c1038c9310b71679b5d10743805d9-7"><code data-line-number=" 7"></code></a></td><td class="code"><code><a id="code_40b21992ea7c1038c9310b71679b5d10743805d9-7" name="code_40b21992ea7c1038c9310b71679b5d10743805d9-7"></a><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;Microsoft.Data.Sqlite&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;10.0.3&quot;</span><span class="nt">/&gt;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_40b21992ea7c1038c9310b71679b5d10743805d9-8"><code data-line-number=" 8"></code></a></td><td class="code"><code><a id="code_40b21992ea7c1038c9310b71679b5d10743805d9-8" name="code_40b21992ea7c1038c9310b71679b5d10743805d9-8"></a><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;Microsoft.Extensions.FileProviders.Physical&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;10.0.3&quot;</span><span class="nt">/&gt;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_40b21992ea7c1038c9310b71679b5d10743805d9-9"><code data-line-number=" 9"></code></a></td><td class="code"><code><a id="code_40b21992ea7c1038c9310b71679b5d10743805d9-9" name="code_40b21992ea7c1038c9310b71679b5d10743805d9-9"></a><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;Microsoft.Extensions.Logging.Console&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;10.0.3&quot;</span><span class="nt">/&gt;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_40b21992ea7c1038c9310b71679b5d10743805d9-10"><code data-line-number="10"></code></a></td><td class="code"><code><a id="code_40b21992ea7c1038c9310b71679b5d10743805d9-10" name="code_40b21992ea7c1038c9310b71679b5d10743805d9-10"></a><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;YamlDotNet&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;16.3.0&quot;</span><span class="nt">/&gt;</span>
</code></td></tr></table></div>
<p>There’s one major thing missing from the above list: code highlighting. <a href="https://www.nuget.org/packages?q=highlight">There are a few highlighting libraries on NuGet</a>, but I decided to stick with <a href="https://pygments.org/">Pygments</a>. I still need the Pygments stylesheets around since I’m not converting old reStructuredText posts to Markdown (I’m copying them as HTML directly from Nikola’s <code>cache</code>), so using Pygments for new content keeps things consistent. Staying with Pygments means I still maintain a bit of Python code, but much less: 230 LoC in <code>pygments_better_html</code> and 89 in <code>yabg_pygments_adapter</code>, with just one third-party dependency. Calling a subprocess while rendering listings is slow, but it’s a price worth paying.</p>
<h3 id="paid-libraries-in-the.net-ecosystem">Paid libraries in the .NET ecosystem</h3>
<p>All the above libraries are open source (MIT, Apache 2.0, BSD-2-Clause). However, one well-known issue of the .NET ecosystem is the number of packages that suddenly become commercial. This trend was started by <a href="https://dotnetfoundation.org/news-events/detail/update-on-imagesharp">ImageSharp</a>, a popular 2D image manipulation library. I could probably use it, since it’s licensed to open-source projects under Apache 2.0, but I’d rather not. I initially tried <a href="https://www.nuget.org/packages/SkiaSharp/">SkiaSharp</a>, but it has terrible image scaling algorithms, so I settled on <a href="https://www.nuget.org/packages/SkiaSharp">Magick.NET</a>.</p>
<p>Open-source sustainability is hard, maybe impossible. But I don’t think transitioning from open-source to pay-for-commercial-use is the answer. In practice, many businesses just use the last free version or switch to a different library. I’d rather support open-source projects developed by volunteers in their spare time. They might not be perfect or always do exactly what I want, but I’m happy to contribute fixes and improve things for everyone. I will avoid proprietary or dual-licensed libraries, even for code that never leaves my computer. Some people complain when Microsoft creates a library that competes with a third-party open-source library (e.g. <a href="https://www.nuget.org/packages/Microsoft.AspNetCore.OpenApi">Microsoft.AspNetCore.OpenApi</a>, which was built to replace <a href="https://www.nuget.org/packages/Swashbuckle.AspNetCore">Swashbuckle.AspNetCore</a>), but I am okay with that, since libraries built or backed by large corporations (like Microsoft) tend to be better maintained.</p>
<p>But at least sometimes <a href="https://www.jimmybogard.com/automapper-and-mediatr-commercial-editions-launch-today/">trash libraries take themselves out</a>.</p>
<h2 id="is-it-fast">Is it fast?</h2>
<p>One of the things that set Nikola apart from other Python static site generators is that it only rebuilds files that need to be rebuild. This does make Nikola fast when rebuilding things, but it comes at a cost: Nikola needs to track all dependencies very closely. Also, some features that are present in other SSGs are not easy to achieve in Nikola, because they would cause many pages to be rebuilt.</p>
<p>YetAnotherBlogGenerator has almost no caching. The only thing currently cached is code listings, since they’re rendered using Pygments in a subprocess. Additionally, the image scaling service checks the file modification date to skip regenerating thumbnails if the source image hasn’t changed. And yet, even if it rewrites everything, YABG finishes faster than Nikola when the site is fully up-to-date (there is nothing to do).</p>
<p>I ran some quick benchmarks comparing the performance of rendering the final Nikola version of this blog against the first YABG version (before the Bootstrap 5 redesign).</p>
<h3 id="testing-methodology">Testing methodology</h3>
<p>Here’s the testing setup:</p>
<ul>
<li>AWS EC2 instances
<ul>
<li>c7a.xlarge (4 vCPU, 8 GB RAM)</li>
<li>30 GB io2 SSD (30000 IOPS)</li>
<li>Total cost: $2.95 + tax for about an hour’s usage ($2.66 of which were storage costs)</li>
</ul>
</li>
<li>Fedora 43 from official Fedora AMI
<ul>
<li>Python 3.14.2 (latest available in the repos)</li>
<li>.NET SDK 10.0.102 / .NET 10.0.2 (latest available in the repos)</li>
<li>setenforce 0, SELINUX=disabled</li>
</ul>
</li>
<li>Windows Server 2025
<ul>
<li>Python 3.14.3 (latest available in winget)</li>
<li>.NET SDK 10.0.103 / .NET 10.0.3 (latest available in winget)</li>
<li>Windows Defender disabled</li>
</ul>
</li>
</ul>
<p>I ran three tests. Each test was run 11 times. The first attempt was discarded (as a warmup and to let me verify the log). The other ten attempts were averaged as the final result. I used PowerShell’s <code>Measure-Command</code> cmdlet for measurements.</p>
<p>The tests were as follows:</p>
<ol>
<li><strong>Clean build (no cache, no output)</strong>
<ul>
<li>Removing <code>.doit.db</code>, <code>cache</code>, and <code>output</code> from the Nikola site, so that everything has to be rebuilt from scratch.</li>
<li>Removing <code>.yabg_cache.sqlite3</code> and <code>output</code> from the YABG site, so that everything has to be reuilt from scratch, most notably the Pygments code listings have to be regenerated via a subprocess.</li>
</ul>
</li>
<li><strong>Build with cache, but no output</strong>
<ul>
<li>Removing <code>output</code> from the Nikola site, so that posts rendered to HTML by docutils/Python-Markdown are cached, but the final HTML still need to be built.</li>
<li>Removing <code>output</code> from the YABG site, so that the code listings rendered to HTML by Pygments are cached, but everything else needs to be built.</li>
</ul>
</li>
<li><strong>Rebuild (cache and output intact)</strong>
<ul>
<li>Not removing anything from the Nikola site, so that there is nothing to do.</li>
<li>Not removing anything from the YABG site. Things are still rebuilt, except for Pygments code listings and thumbnails.</li>
</ul>
</li>
</ol>
<p>For YetAnotherBlogGenerator, I tested two builds: one in Release mode (standard), and another in <a href="https://learn.microsoft.com/en-us/dotnet/core/deploying/ready-to-run">ReadyToRun mode</a>, trading build time and executable size for faster execution.</p>
<p>All the scripts I used for setup and testing can be found in <a href="https://chriswarrick.com/listings/yabg-intro/speedtest/">listings</a>.</p>
<h3 id="test-results">Test results</h3>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Platform</th>
<th>Build type</th>
<th style="text-align: right;">Nikola</th>
<th style="text-align: right;">YABG (ReadyToRun)</th>
<th style="text-align: right;">YABG (Release)</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Linux</strong></td>
<td>Clean build (no cache, no output)</td>
<td style="text-align: right;">6.438</td>
<td style="text-align: right;">1.901</td>
<td style="text-align: right;">2.178</td>
</tr>
<tr>
<td><strong>Linux</strong></td>
<td>Build with cache, but no output</td>
<td style="text-align: right;">5.418</td>
<td style="text-align: right;">0.980</td>
<td style="text-align: right;">1.249</td>
</tr>
<tr>
<td><strong>Linux</strong></td>
<td>Rebuild (cache and output intact)</td>
<td style="text-align: right;">0.997</td>
<td style="text-align: right;">0.969</td>
<td style="text-align: right;">1.248</td>
</tr>
<tr>
<td><strong>Windows</strong></td>
<td>Clean build (no cache, no output)</td>
<td style="text-align: right;">9.103</td>
<td style="text-align: right;">2.666</td>
<td style="text-align: right;">2.941</td>
</tr>
<tr>
<td><strong>Windows</strong></td>
<td>Build with cache, but no output</td>
<td style="text-align: right;">7.758</td>
<td style="text-align: right;">1.051</td>
<td style="text-align: right;">1.333</td>
</tr>
<tr>
<td><strong>Windows</strong></td>
<td>Rebuild (cache and output intact)</td>
<td style="text-align: right;">1.562</td>
<td style="text-align: right;">1.020</td>
<td style="text-align: right;">1.297</td>
</tr>
</tbody>
</table>
</div><h2 id="design-details-and-highlights">Design details and highlights</h2>
<p>Here are some fun tidbits from development.</p>
<h3 id="everything-is-an-item">Everything is an item</h3>
<p>In Nikola, there are several different entities that can generate HTML files. Posts and Pages are both <code>Post</code> objects. Listings and galleries each have their own task generators. There’s no <code>Listing</code> class, everything is handled within the listing plugin. Galleries can optionally have a <code>Post</code> object attached (though that <code>Post</code> is not picked up by the file scanner, and it is not part of the timeline). The listings and galleries task generators both have ways to build directory trees.</p>
<p>In YABG, all of the above are <code>Item</code>s. Specifically, they start as <code>SourceItem</code>s and become <code>Item</code>s when rendered. For listings, the source is just the code and the rendered content is Pygments-generated HTML. For galleries, the source is a <a href="https://en.wikipedia.org/wiki/Tab-separated_values">TSV file</a> with a list of included gallery images (order, filenames, and descriptions), and the generated content comes from a meta field named <code>galleryIntroHtml</code>. Gallery objects have a <code>GalleryData</code> object attached to their <code>Item</code> object as <code>RichItemData</code>.</p>
<p>This simplifies the final rendering pipeline design. Only four classes (actual classes, not temporary structures in some plugin) can render to HTML: <code>Item</code>, <code>ItemGroup</code> (tags, categories, yearly archives, gallery indexes), <code>DirectoryTreeGroup</code> (listings), and <code>LinkGroup</code> (archive and tag indexes). Each has a corresponding template model. Nikola’s sitemap generator recurses through the <code>output</code> directory to find files, but YABG can just use the lists of items and groups. The sitemap won’t include HTML files from the files folder, but I don’t need them there (though I could add them if needed).</p>
<h3 id="windows-first-linux-in-zero-time">Windows first, Linux in zero time</h3>
<p>I developed YABG entirely on Windows. This forced me to think about paths and URLs as separate concepts. I couldn’t use most <code>System.IO.Path</code> facilities for URLs, since they would produce backslashes. As a result, there are zero bugs where backslashes leak into output on Windows. Nikola has such bugs pop up occasionally; indeed, <a href="https://github.com/getnikola/nikola/commit/d8d94c047cdc1718700f0b5d00627722241be68d">I fixed one yesterday</a>.</p>
<p>But when YABG was nearly complete, I ran it on Linux. And it just worked. No code changes needed. No output differences. (I had to add <code>SkiaSharp.NativeAssets.Linux</code> and <code>apt install libfontconfig1</code> since I was stilll using SkiaSharp at that point, but that’s no longer needed with Magick.NET.)</p>
<p>Not everything is perfect, though. I added a <code>--watch</code> mode based on <code>FileSystemWatcher</code>, but it doesn’t work on Linux. I don’t <em>need</em> it there; I’d have to switch to polling to make it work.</p>
<h3 id="dependency-injection-everywhere">Dependency injection everywhere</h3>
<p>A good principle used in object-oriented development (though not very often in Python) is <strong>dependency injection</strong>.  I have several grouping services, all implementing either <code>IPostGrouper</code> or <code>IItemGrouper</code>. They’re registered in the DI container as implementations of those interfaces. The <code>GroupEngine</code> doesn’t need to know about specific group types, it just gets them from the container and passes the post and item arrays.</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_c29b1ed1e30a3b34f36f210df79414927ba24273-1"><code data-line-number="1"></code></a></td><td class="code"><code><a id="code_c29b1ed1e30a3b34f36f210df79414927ba24273-1" name="code_c29b1ed1e30a3b34f36f210df79414927ba24273-1"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">AddScoped</span><span class="o">&lt;</span><span class="n">IPostGrouper</span><span class="p">,</span><span class="w"> </span><span class="n">ArchiveGrouper</span><span class="o">&gt;</span><span class="p">()</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_c29b1ed1e30a3b34f36f210df79414927ba24273-2"><code data-line-number="2"></code></a></td><td class="code"><code><a id="code_c29b1ed1e30a3b34f36f210df79414927ba24273-2" name="code_c29b1ed1e30a3b34f36f210df79414927ba24273-2"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">AddScoped</span><span class="o">&lt;</span><span class="n">IPostGrouper</span><span class="p">,</span><span class="w"> </span><span class="n">GuideGrouper</span><span class="o">&gt;</span><span class="p">()</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_c29b1ed1e30a3b34f36f210df79414927ba24273-3"><code data-line-number="3"></code></a></td><td class="code"><code><a id="code_c29b1ed1e30a3b34f36f210df79414927ba24273-3" name="code_c29b1ed1e30a3b34f36f210df79414927ba24273-3"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">AddScoped</span><span class="o">&lt;</span><span class="n">IPostGrouper</span><span class="p">,</span><span class="w"> </span><span class="n">IndexGrouper</span><span class="o">&gt;</span><span class="p">()</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_c29b1ed1e30a3b34f36f210df79414927ba24273-4"><code data-line-number="4"></code></a></td><td class="code"><code><a id="code_c29b1ed1e30a3b34f36f210df79414927ba24273-4" name="code_c29b1ed1e30a3b34f36f210df79414927ba24273-4"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">AddScoped</span><span class="o">&lt;</span><span class="n">IPostGrouper</span><span class="p">,</span><span class="w"> </span><span class="n">NavigationGrouper</span><span class="o">&gt;</span><span class="p">()</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_c29b1ed1e30a3b34f36f210df79414927ba24273-5"><code data-line-number="5"></code></a></td><td class="code"><code><a id="code_c29b1ed1e30a3b34f36f210df79414927ba24273-5" name="code_c29b1ed1e30a3b34f36f210df79414927ba24273-5"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">AddScoped</span><span class="o">&lt;</span><span class="n">IPostGrouper</span><span class="p">,</span><span class="w"> </span><span class="n">TagCategoryGrouper</span><span class="o">&gt;</span><span class="p">()</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_c29b1ed1e30a3b34f36f210df79414927ba24273-6"><code data-line-number="6"></code></a></td><td class="code"><code><a id="code_c29b1ed1e30a3b34f36f210df79414927ba24273-6" name="code_c29b1ed1e30a3b34f36f210df79414927ba24273-6"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">AddScoped</span><span class="o">&lt;</span><span class="n">IItemGrouper</span><span class="p">,</span><span class="w"> </span><span class="n">GalleryIndexGrouper</span><span class="o">&gt;</span><span class="p">()</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_c29b1ed1e30a3b34f36f210df79414927ba24273-7"><code data-line-number="7"></code></a></td><td class="code"><code><a id="code_c29b1ed1e30a3b34f36f210df79414927ba24273-7" name="code_c29b1ed1e30a3b34f36f210df79414927ba24273-7"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">AddScoped</span><span class="o">&lt;</span><span class="n">IItemGrouper</span><span class="p">,</span><span class="w"> </span><span class="n">ListingIndexGrouper</span><span class="o">&gt;</span><span class="p">()</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_c29b1ed1e30a3b34f36f210df79414927ba24273-8"><code data-line-number="8"></code></a></td><td class="code"><code><a id="code_c29b1ed1e30a3b34f36f210df79414927ba24273-8" name="code_c29b1ed1e30a3b34f36f210df79414927ba24273-8"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">AddScoped</span><span class="o">&lt;</span><span class="n">IItemGrouper</span><span class="p">,</span><span class="w"> </span><span class="n">ProjectGrouper</span><span class="o">&gt;</span><span class="p">()</span>
</code></td></tr></table></div>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-1"><code data-line-number=" 1"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-1" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-1"></a><span class="k">internal</span><span class="w"> </span><span class="k">class</span><span class="w"> </span><span class="nf">GroupEngine</span><span class="p">(</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-2"><code data-line-number=" 2"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-2" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-2"></a><span class="w">&nbsp;&nbsp;</span><span class="n">IEnumerable</span><span class="o">&lt;</span><span class="n">IItemGrouper</span><span class="o">&gt;</span><span class="w"> </span><span class="n">itemGroupers</span><span class="p">,</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-3"><code data-line-number=" 3"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-3" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-3"></a><span class="w">&nbsp;&nbsp;</span><span class="n">IEnumerable</span><span class="o">&lt;</span><span class="n">IPostGrouper</span><span class="o">&gt;</span><span class="w"> </span><span class="n">postGroupers</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-4"><code data-line-number=" 4"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-4" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-4"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">:</span><span class="w"> </span><span class="n">IGroupEngine</span><span class="w"> </span><span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-5"><code data-line-number=" 5"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-5" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-5"></a><span class="w">&nbsp;&nbsp;</span><span class="k">public</span><span class="w"> </span><span class="n">IEnumerable</span><span class="o">&lt;</span><span class="n">IGroup</span><span class="o">&gt;</span><span class="w"> </span><span class="n">GenerateGroups</span><span class="p">(</span><span class="n">Item</span><span class="p">[]</span><span class="w"> </span><span class="n">items</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-6"><code data-line-number=" 6"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-6" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-6"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="kt">var</span><span class="w"> </span><span class="n">sortedItems</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">items</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-7"><code data-line-number=" 7"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-7" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-7"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">OrderByDescending</span><span class="p">(</span><span class="n">i</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">i</span><span class="p">.</span><span class="n">Published</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-8"><code data-line-number=" 8"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-8" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-8"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">ThenBy</span><span class="p">(</span><span class="n">i</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">i</span><span class="p">.</span><span class="n">SourcePath</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-9"><code data-line-number=" 9"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-9" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-9"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">ToArray</span><span class="p">();</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-10"><code data-line-number="10"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-10" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-10"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-11"><code data-line-number="11"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-11" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-11"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="kt">var</span><span class="w"> </span><span class="n">sortedPosts</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sortedItems</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-12"><code data-line-number="12"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-12" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-12"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">Where</span><span class="p">(</span><span class="n">item</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">item</span><span class="p">.</span><span class="n">Type</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">ItemType</span><span class="p">.</span><span class="n">Post</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-13"><code data-line-number="13"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-13" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-13"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">ToArray</span><span class="p">();</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-14"><code data-line-number="14"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-14" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-14"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-15"><code data-line-number="15"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-15" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-15"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="kt">var</span><span class="w"> </span><span class="n">itemGroups</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">itemGroupers</span><span class="p">.</span><span class="n">SelectMany</span><span class="p">(</span><span class="n">g</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">g</span><span class="p">.</span><span class="n">GroupItems</span><span class="p">(</span><span class="n">sortedItems</span><span class="p">));</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-16"><code data-line-number="16"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-16" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-16"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="kt">var</span><span class="w"> </span><span class="n">postGroups</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">postGroupers</span><span class="p">.</span><span class="n">SelectMany</span><span class="p">(</span><span class="n">g</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">g</span><span class="p">.</span><span class="n">GroupPosts</span><span class="p">(</span><span class="n">sortedPosts</span><span class="p">));</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-17"><code data-line-number="17"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-17" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-17"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="k">return</span><span class="w"> </span><span class="n">itemGroups</span><span class="p">.</span><span class="n">Concat</span><span class="p">(</span><span class="n">postGroups</span><span class="p">);</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-18"><code data-line-number="18"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-18" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-18"></a><span class="w">&nbsp;&nbsp;</span><span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-19"><code data-line-number="19"></code></a></td><td class="code"><code><a id="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-19" name="code_badc33d3269d7f0a3c6b6177683f5988ea9b2ab3-19"></a><span class="p">}</span>
</code></td></tr></table></div>
<p>The <code>ItemRenderEngine</code> has a slightly different challenge: it needs to pick the correct renderer for the post (Gallery, HTML, Listing, Markdown). The renderers are registered as keyed services. The render engine does not need to know anything about the specific renderer types, it just gets the renderer name from the <code>SourceItem</code>’s <code>ScanPattern</code> (so ultimately from the configuration file) and asks the DI container to provide it with the right implementation.</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_b40867aeb78b11d9d5f25c39bec3682c19216014-1"><code data-line-number="1"></code></a></td><td class="code"><code><a id="code_b40867aeb78b11d9d5f25c39bec3682c19216014-1" name="code_b40867aeb78b11d9d5f25c39bec3682c19216014-1"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">AddKeyedScoped</span><span class="o">&lt;</span><span class="n">IItemRenderer</span><span class="p">,</span><span class="w"> </span><span class="n">GalleryItemRenderer</span><span class="o">&gt;</span><span class="p">(</span><span class="n">GalleryItemRenderer</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_b40867aeb78b11d9d5f25c39bec3682c19216014-2"><code data-line-number="2"></code></a></td><td class="code"><code><a id="code_b40867aeb78b11d9d5f25c39bec3682c19216014-2" name="code_b40867aeb78b11d9d5f25c39bec3682c19216014-2"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">AddKeyedScoped</span><span class="o">&lt;</span><span class="n">IItemRenderer</span><span class="p">,</span><span class="w"> </span><span class="n">HtmlItemRenderer</span><span class="o">&gt;</span><span class="p">(</span><span class="n">HtmlItemRenderer</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_b40867aeb78b11d9d5f25c39bec3682c19216014-3"><code data-line-number="3"></code></a></td><td class="code"><code><a id="code_b40867aeb78b11d9d5f25c39bec3682c19216014-3" name="code_b40867aeb78b11d9d5f25c39bec3682c19216014-3"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">AddKeyedScoped</span><span class="o">&lt;</span><span class="n">IItemRenderer</span><span class="p">,</span><span class="w"> </span><span class="n">ListingItemRenderer</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ListingItemRenderer</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_b40867aeb78b11d9d5f25c39bec3682c19216014-4"><code data-line-number="4"></code></a></td><td class="code"><code><a id="code_b40867aeb78b11d9d5f25c39bec3682c19216014-4" name="code_b40867aeb78b11d9d5f25c39bec3682c19216014-4"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">AddKeyedScoped</span><span class="o">&lt;</span><span class="n">IItemRenderer</span><span class="p">,</span><span class="w"> </span><span class="n">MarkdownItemRenderer</span><span class="o">&gt;</span><span class="p">(</span><span class="n">MarkdownItemRenderer</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
</code></td></tr></table></div>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-1"><code data-line-number=" 1"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-1" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-1"></a><span class="w">&nbsp;&nbsp;</span><span class="k">public</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="n">Task</span><span class="o">&lt;</span><span class="n">IEnumerable</span><span class="o">&lt;</span><span class="n">Item</span><span class="o">&gt;&gt;</span><span class="w"> </span><span class="n">Render</span><span class="p">(</span><span class="n">IEnumerable</span><span class="o">&lt;</span><span class="n">SourceItem</span><span class="o">&gt;</span><span class="w"> </span><span class="n">sourceItems</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-2"><code data-line-number=" 2"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-2" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-2"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="kt">var</span><span class="w"> </span><span class="n">renderTasks</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sourceItems</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-3"><code data-line-number=" 3"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-3" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-3"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">GroupBy</span><span class="p">(</span><span class="n">i</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">i</span><span class="p">.</span><span class="n">ScanPattern</span><span class="p">.</span><span class="n">RendererName</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-4"><code data-line-number=" 4"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-4" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-4"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">Select</span><span class="p">(</span><span class="k">group</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-5"><code data-line-number=" 5"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-5" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-5"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="kt">var</span><span class="w"> </span><span class="n">renderer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">_keyedServiceProvider</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-6"><code data-line-number=" 6"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-6" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-6"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">.</span><span class="n">GetRequiredKeyedService</span><span class="o">&lt;</span><span class="n">IItemRenderer</span><span class="o">&gt;</span><span class="p">(</span><span class="k">group</span><span class="p">.</span><span class="n">Key</span><span class="p">);</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-7"><code data-line-number=" 7"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-7" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-7"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="k">return</span><span class="w"> </span><span class="n">renderer</span><span class="w"> </span><span class="k">switch</span><span class="w"> </span><span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-8"><code data-line-number=" 8"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-8" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-8"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="n">IBulkItemRenderer</span><span class="w"> </span><span class="n">bulkRenderer</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">bulkRenderer</span><span class="p">.</span><span class="n">RenderItems</span><span class="p">(</span><span class="k">group</span><span class="p">),</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-9"><code data-line-number=" 9"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-9" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-9"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="n">ISingleItemRenderer</span><span class="w"> </span><span class="n">singleRenderer</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">Task</span><span class="p">.</span><span class="n">WhenAll</span><span class="p">(</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-10"><code data-line-number="10"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-10" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-10"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="k">group</span><span class="p">.</span><span class="n">Select</span><span class="p">(</span><span class="n">singleRenderer</span><span class="p">.</span><span class="n">RenderItem</span><span class="p">)),</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-11"><code data-line-number="11"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-11" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-11"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="n">_</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">&quot;Unexpected renderer type&quot;</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-12"><code data-line-number="12"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-12" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-12"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">};</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-13"><code data-line-number="13"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-13" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-13"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">});</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2026/02/16/i-wrote-yet-another-blog-generator/#code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-14"><code data-line-number="14"></code></a></td><td class="code"><code><a id="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-14" name="code_10e51bbfc1002bc3932ecb96e587f1b3dad5958b-14"></a><span class="w">&nbsp;&nbsp;</span><span class="p">}</span>
</code></td></tr></table></div>
<p>In total, there are <strong>37</strong> specific service implementations registered (plus system services like <code>TimeProvider</code> and logging). Beyond these two examples, the main benefit is <strong>testability</strong>. I can write unit tests without dependencies on unrelated services, and without monkey-patching random names. (In Python, <code>unittest.mock</code> does both monkey-patching <em>and</em> mocking.)</p>
<p>Okay, I haven’t written very many tests, but I could easily ask an LLM to do it.</p>
<h3 id="immutable-data-structures-and-no-global-state">Immutable data structures and no global state</h3>
<p>All classes are immutable. This helps in several ways. It’s easier to reason about state when <code>SourceItem</code> becomes <code>Item</code> during rendering, compared to a single class with a nullable <code>Content</code> property. Immutability also makes concurrency safer. But the biggest win is how easy it was to develop the <code>--watch</code> mode. Every service has <code>Scoped</code> lifetime, and main logic lives in <code>IMainEngine</code>. I can just create a new scope, get the engine, and run it without state leaking between executions. No subprocess launching, no state resetting — everything disappears when the scope is disposed.</p>
<h2 id="can-anyone-use-it">Can anyone use it?</h2>
<p>On one hand, it’s open source under the 3-clause BSD license and <a href="https://github.com/Kwpolska/YetAnotherBlogGenerator">available on GitHub</a>.</p>
<p>On the other hand, it’s more of a source-available project. There are no docs, and it was designed specifically for this site (so some things are probably too hardcoded for your needs). In fact, this blog’s configuration and templates were directly hardcoded in the codebase until the day before launch. But I’m happy to answer questions and review pull requests!</p>
]]></content:encoded><category>C#/.NET</category><category>.NET</category><category>C#</category><category>Nikola</category><category>Python</category><category>static site generators</category><category>web development</category><category>YetAnotherBlogGenerator</category></item><item><title>PowerShell: the object-oriented shell you didn’t know you needed</title><dc:creator>Chris Warrick</dc:creator><link>https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/</link><pubDate>Mon, 29 Apr 2024 16:45:00 GMT</pubDate><guid>https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/</guid><description>PowerShell is an interactive shell and scripting language from Microsoft. It’s object-oriented — and that’s not just a buzzword, that’s a big difference to how the standard Unix shells work. And it is actually usable as an interactive shell.
</description><content:encoded><![CDATA[<p>PowerShell is an interactive shell and scripting language from Microsoft. It’s object-oriented — and that’s not just a buzzword, that’s a big difference to how the standard Unix shells work. And it is actually usable as an interactive shell.</p>



<h2 id="getting-started">Getting Started</h2>
<p>PowerShell is so nice, Microsoft made it twice.</p>
<p>Specifically, there concurrently exist two products named PowerShell:</p>
<ul>
<li>Windows PowerShell (5.1) is a built-in component of Windows. It is proprietary, Windows-only, and is based on the equally proprietary and equally Windows-only .NET Framework 4.x. It has a blue icon.</li>
<li>PowerShell (7.x), formerly known as PowerShell Core, is a stand-alone application. It is MIT-licensed <a href="https://github.com/PowerShell/PowerShell">(developed on GitHub)</a>, available for Windows, Linux, and macOS, and is based on the equally MIT-licensed and equally multi-platform .NET (formerly .NET Core). It has a black icon.</li>
</ul>
<p>Windows PowerShell development stopped when PowerShell (Core) came out. There are some niceties and commands missing in it, but it is still a fine option for trying it out or for when one can’t install PowerShell on a Windows system but need to solve something with code.</p>
<p>All examples in this post should work in either version of PowerShell on any OS (unless explicitly noted otherwise).</p>
<p>Install the modern PowerShell: <a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4">Windows</a>, <a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux?view=powershell-7.4">Linux</a>, <a href="https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux?view=powershell-7.4">macOS</a>.</p>
<h2 id="objects-in-my-shell">Objects? In my shell?</h2>
<p>Let’s try getting a directory listing. This is Microsoft land, so let’s try the DOS command for a directory listing — that would be <code>dir</code>:</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_5487e1609201ce902c883e7cd5ea641dfe46849f-1"><code data-line-number=" 1"></code></a></td><td class="code"><code><a id="code_5487e1609201ce902c883e7cd5ea641dfe46849f-1" name="code_5487e1609201ce902c883e7cd5ea641dfe46849f-1"></a><span class="nb">PS </span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span><span class="p">&gt;</span> <span class="nb">dir</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_5487e1609201ce902c883e7cd5ea641dfe46849f-2"><code data-line-number=" 2"></code></a></td><td class="code"><code><a id="code_5487e1609201ce902c883e7cd5ea641dfe46849f-2" name="code_5487e1609201ce902c883e7cd5ea641dfe46849f-2"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_5487e1609201ce902c883e7cd5ea641dfe46849f-3"><code data-line-number=" 3"></code></a></td><td class="code"><code><a id="code_5487e1609201ce902c883e7cd5ea641dfe46849f-3" name="code_5487e1609201ce902c883e7cd5ea641dfe46849f-3"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">Directory</span><span class="p">:</span> <span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_5487e1609201ce902c883e7cd5ea641dfe46849f-4"><code data-line-number=" 4"></code></a></td><td class="code"><code><a id="code_5487e1609201ce902c883e7cd5ea641dfe46849f-4" name="code_5487e1609201ce902c883e7cd5ea641dfe46849f-4"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_5487e1609201ce902c883e7cd5ea641dfe46849f-5"><code data-line-number=" 5"></code></a></td><td class="code"><code><a id="code_5487e1609201ce902c883e7cd5ea641dfe46849f-5" name="code_5487e1609201ce902c883e7cd5ea641dfe46849f-5"></a><span class="n">Mode</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">LastWriteTime</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">Length</span> <span class="n">Name</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_5487e1609201ce902c883e7cd5ea641dfe46849f-6"><code data-line-number=" 6"></code></a></td><td class="code"><code><a id="code_5487e1609201ce902c883e7cd5ea641dfe46849f-6" name="code_5487e1609201ce902c883e7cd5ea641dfe46849f-6"></a><span class="p">----</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">-------------</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">------</span> <span class="p">----</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_5487e1609201ce902c883e7cd5ea641dfe46849f-7"><code data-line-number=" 7"></code></a></td><td class="code"><code><a id="code_5487e1609201ce902c883e7cd5ea641dfe46849f-7" name="code_5487e1609201ce902c883e7cd5ea641dfe46849f-7"></a><span class="n">d</span><span class="p">----</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">2024</span><span class="p">-</span><span class="n">04</span><span class="p">-</span><span class="n">29</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">18</span><span class="p">:</span><span class="n">00</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">world</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_5487e1609201ce902c883e7cd5ea641dfe46849f-8"><code data-line-number=" 8"></code></a></td><td class="code"><code><a id="code_5487e1609201ce902c883e7cd5ea641dfe46849f-8" name="code_5487e1609201ce902c883e7cd5ea641dfe46849f-8"></a><span class="n">-a</span><span class="p">---</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">2024</span><span class="p">-</span><span class="n">04</span><span class="p">-</span><span class="n">29</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">18</span><span class="p">:</span><span class="n">00</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">23</span> <span class="n">example</span><span class="p">.</span><span class="n">py</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_5487e1609201ce902c883e7cd5ea641dfe46849f-9"><code data-line-number=" 9"></code></a></td><td class="code"><code><a id="code_5487e1609201ce902c883e7cd5ea641dfe46849f-9" name="code_5487e1609201ce902c883e7cd5ea641dfe46849f-9"></a><span class="n">-a</span><span class="p">---</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">2024</span><span class="p">-</span><span class="n">04</span><span class="p">-</span><span class="n">29</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">18</span><span class="p">:</span><span class="n">00</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">7</span> <span class="n">foobar</span><span class="p">.</span><span class="n">txt</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_5487e1609201ce902c883e7cd5ea641dfe46849f-10"><code data-line-number="10"></code></a></td><td class="code"><code><a id="code_5487e1609201ce902c883e7cd5ea641dfe46849f-10" name="code_5487e1609201ce902c883e7cd5ea641dfe46849f-10"></a><span class="n">-a</span><span class="p">---</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">2024</span><span class="p">-</span><span class="n">04</span><span class="p">-</span><span class="n">29</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">18</span><span class="p">:</span><span class="n">00</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">14</span> <span class="n">helloworld</span><span class="p">.</span><span class="n">txt</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_5487e1609201ce902c883e7cd5ea641dfe46849f-11"><code data-line-number="11"></code></a></td><td class="code"><code><a id="code_5487e1609201ce902c883e7cd5ea641dfe46849f-11" name="code_5487e1609201ce902c883e7cd5ea641dfe46849f-11"></a><span class="n">-a</span><span class="p">---</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">2024</span><span class="p">-</span><span class="n">04</span><span class="p">-</span><span class="n">29</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">18</span><span class="p">:</span><span class="n">00</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">0</span> <span class="n">newfile</span><span class="p">.</span><span class="n">txt</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_5487e1609201ce902c883e7cd5ea641dfe46849f-12"><code data-line-number="12"></code></a></td><td class="code"><code><a id="code_5487e1609201ce902c883e7cd5ea641dfe46849f-12" name="code_5487e1609201ce902c883e7cd5ea641dfe46849f-12"></a><span class="n">-a</span><span class="p">---</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">2024</span><span class="p">-</span><span class="n">04</span><span class="p">-</span><span class="n">29</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">18</span><span class="p">:</span><span class="n">00</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">5</span> <span class="n">test</span><span class="p">.</span><span class="n">txt</span>
</code></td></tr></table></div>
<p>This looks like a typical (if slightly verbose) file listing.</p>
<p>Now, let’s try to do something useful with this. Let’s get the total size of all <code>.txt</code> files.</p>
<p>In a Unix shell, one option is <code>du -bc *.txt</code>. The arguments: <code>-b</code> (<code>--bytes</code>) gives the real byte size, and <code>-c</code> (<code>--summarize</code>) produces a total. The result is this:</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2026e78c6a1fda66be195947828c8cd5292e1e12-1"><code data-line-number="1"></code></a></td><td class="code"><code><a id="code_2026e78c6a1fda66be195947828c8cd5292e1e12-1" name="code_2026e78c6a1fda66be195947828c8cd5292e1e12-1"></a>7&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foobar.txt
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2026e78c6a1fda66be195947828c8cd5292e1e12-2"><code data-line-number="2"></code></a></td><td class="code"><code><a id="code_2026e78c6a1fda66be195947828c8cd5292e1e12-2" name="code_2026e78c6a1fda66be195947828c8cd5292e1e12-2"></a>14&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;helloworld.txt
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2026e78c6a1fda66be195947828c8cd5292e1e12-3"><code data-line-number="3"></code></a></td><td class="code"><code><a id="code_2026e78c6a1fda66be195947828c8cd5292e1e12-3" name="code_2026e78c6a1fda66be195947828c8cd5292e1e12-3"></a>0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newfile.txt
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2026e78c6a1fda66be195947828c8cd5292e1e12-4"><code data-line-number="4"></code></a></td><td class="code"><code><a id="code_2026e78c6a1fda66be195947828c8cd5292e1e12-4" name="code_2026e78c6a1fda66be195947828c8cd5292e1e12-4"></a>5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;test.txt
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2026e78c6a1fda66be195947828c8cd5292e1e12-5"><code data-line-number="5"></code></a></td><td class="code"><code><a id="code_2026e78c6a1fda66be195947828c8cd5292e1e12-5" name="code_2026e78c6a1fda66be195947828c8cd5292e1e12-5"></a>26&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;total
</code></td></tr></table></div>
<p>But how to get just the number? This requires text manipulation (getting the first word of the last line). Something like <code>du -bc *.txt | tail -n 1 | cut -f 1</code> will do. There’s also <code>wc --total=only --bytes *.txt</code> — but this is specific to GNU wc, so it won’t cut it on *BSD or macOS. Another option would be to parse the output of <code>ls -l</code> — but that might not always be easy, and the output may contain something unexpected added by the specific <code>ls</code> version or the user’s specific shell configuration.</p>
<p>Let’s try something in PowerShell. If we do <code>$x = dir</code>, we’ll have the output of the <code>dir</code> command in <code>$x</code>. Let’s try to analyse it further, is the first character a newline?</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_732adae82ccca20456365dc143dee4ccde7d735d-1"><code data-line-number="1"></code></a></td><td class="code"><code><a id="code_732adae82ccca20456365dc143dee4ccde7d735d-1" name="code_732adae82ccca20456365dc143dee4ccde7d735d-1"></a><span class="nb">PS </span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span><span class="p">&gt;</span> <span class="nv">$x</span> <span class="p">=</span> <span class="nb">dir</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_732adae82ccca20456365dc143dee4ccde7d735d-2"><code data-line-number="2"></code></a></td><td class="code"><code><a id="code_732adae82ccca20456365dc143dee4ccde7d735d-2" name="code_732adae82ccca20456365dc143dee4ccde7d735d-2"></a><span class="nb">PS </span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span><span class="p">&gt;</span> <span class="nv">$x</span><span class="p">[</span><span class="n">0</span><span class="p">]</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_732adae82ccca20456365dc143dee4ccde7d735d-3"><code data-line-number="3"></code></a></td><td class="code"><code><a id="code_732adae82ccca20456365dc143dee4ccde7d735d-3" name="code_732adae82ccca20456365dc143dee4ccde7d735d-3"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_732adae82ccca20456365dc143dee4ccde7d735d-4"><code data-line-number="4"></code></a></td><td class="code"><code><a id="code_732adae82ccca20456365dc143dee4ccde7d735d-4" name="code_732adae82ccca20456365dc143dee4ccde7d735d-4"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">Directory</span><span class="p">:</span> <span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_732adae82ccca20456365dc143dee4ccde7d735d-5"><code data-line-number="5"></code></a></td><td class="code"><code><a id="code_732adae82ccca20456365dc143dee4ccde7d735d-5" name="code_732adae82ccca20456365dc143dee4ccde7d735d-5"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_732adae82ccca20456365dc143dee4ccde7d735d-6"><code data-line-number="6"></code></a></td><td class="code"><code><a id="code_732adae82ccca20456365dc143dee4ccde7d735d-6" name="code_732adae82ccca20456365dc143dee4ccde7d735d-6"></a><span class="n">Mode</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">LastWriteTime</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">Length</span> <span class="n">Name</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_732adae82ccca20456365dc143dee4ccde7d735d-7"><code data-line-number="7"></code></a></td><td class="code"><code><a id="code_732adae82ccca20456365dc143dee4ccde7d735d-7" name="code_732adae82ccca20456365dc143dee4ccde7d735d-7"></a><span class="p">----</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">-------------</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">------</span> <span class="p">----</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_732adae82ccca20456365dc143dee4ccde7d735d-8"><code data-line-number="8"></code></a></td><td class="code"><code><a id="code_732adae82ccca20456365dc143dee4ccde7d735d-8" name="code_732adae82ccca20456365dc143dee4ccde7d735d-8"></a><span class="n">d</span><span class="p">----</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">2024</span><span class="p">-</span><span class="n">04</span><span class="p">-</span><span class="n">29</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">18</span><span class="p">:</span><span class="n">00</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">world</span>
</code></td></tr></table></div>
<p>That’s interesting, we didn’t get the first character or the first line, we got the first <em>file</em>. And if we try <code>$x[1]</code>?</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a0872bfab1d587bc08f128eb0a76b8101473a096-1"><code data-line-number="1"></code></a></td><td class="code"><code><a id="code_a0872bfab1d587bc08f128eb0a76b8101473a096-1" name="code_a0872bfab1d587bc08f128eb0a76b8101473a096-1"></a><span class="nb">PS </span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span><span class="p">&gt;</span> <span class="nv">$x</span><span class="p">[</span><span class="n">1</span><span class="p">]</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a0872bfab1d587bc08f128eb0a76b8101473a096-2"><code data-line-number="2"></code></a></td><td class="code"><code><a id="code_a0872bfab1d587bc08f128eb0a76b8101473a096-2" name="code_a0872bfab1d587bc08f128eb0a76b8101473a096-2"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a0872bfab1d587bc08f128eb0a76b8101473a096-3"><code data-line-number="3"></code></a></td><td class="code"><code><a id="code_a0872bfab1d587bc08f128eb0a76b8101473a096-3" name="code_a0872bfab1d587bc08f128eb0a76b8101473a096-3"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">Directory</span><span class="p">:</span> <span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a0872bfab1d587bc08f128eb0a76b8101473a096-4"><code data-line-number="4"></code></a></td><td class="code"><code><a id="code_a0872bfab1d587bc08f128eb0a76b8101473a096-4" name="code_a0872bfab1d587bc08f128eb0a76b8101473a096-4"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a0872bfab1d587bc08f128eb0a76b8101473a096-5"><code data-line-number="5"></code></a></td><td class="code"><code><a id="code_a0872bfab1d587bc08f128eb0a76b8101473a096-5" name="code_a0872bfab1d587bc08f128eb0a76b8101473a096-5"></a><span class="n">Mode</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">LastWriteTime</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">Length</span> <span class="n">Name</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a0872bfab1d587bc08f128eb0a76b8101473a096-6"><code data-line-number="6"></code></a></td><td class="code"><code><a id="code_a0872bfab1d587bc08f128eb0a76b8101473a096-6" name="code_a0872bfab1d587bc08f128eb0a76b8101473a096-6"></a><span class="p">----</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">-------------</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">------</span> <span class="p">----</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a0872bfab1d587bc08f128eb0a76b8101473a096-7"><code data-line-number="7"></code></a></td><td class="code"><code><a id="code_a0872bfab1d587bc08f128eb0a76b8101473a096-7" name="code_a0872bfab1d587bc08f128eb0a76b8101473a096-7"></a><span class="n">-a</span><span class="p">---</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">2024</span><span class="p">-</span><span class="n">04</span><span class="p">-</span><span class="n">29</span>&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">18</span><span class="p">:</span><span class="n">00</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">23</span> <span class="n">example</span><span class="p">.</span><span class="n">py</span>
</code></td></tr></table></div>
<p>What if we try getting the <code>Length</code> property out of that?</p>
<div class="highlight"><pre><span></span><span class="nb">PS </span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span><span class="p">&gt;</span> <span class="nv">$x</span><span class="p">[</span><span class="n">1</span><span class="p">].</span><span class="n">Length</span>
<span class="n">23</span>
</pre></div>

<p>It turns out that <code>dir</code> returns an array of objects, and PowerShell knows how to format this array (and a single item from the array) into a nice table. What can we do with it? This:</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-1"><code data-line-number=" 1"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-1" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-1"></a><span class="nb">PS </span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span><span class="p">&gt;</span> <span class="nb">Get-ChildItem</span> <span class="n">-Filter</span> <span class="s1">&#39;*.txt&#39;</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-2"><code data-line-number=" 2"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-2" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-2"></a>&nbsp;&nbsp;<span class="k">ForEach</span><span class="n">-Object</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Length</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-3"><code data-line-number=" 3"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-3" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-3"></a>&nbsp;&nbsp;<span class="nb">Measure-Object</span> <span class="n">-Sum</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-4"><code data-line-number=" 4"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-4" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-4"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-5"><code data-line-number=" 5"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-5" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-5"></a><span class="n">Count</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">:</span> <span class="n">4</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-6"><code data-line-number=" 6"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-6" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-6"></a><span class="n">Average</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">:</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-7"><code data-line-number=" 7"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-7" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-7"></a><span class="n">Sum</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">:</span> <span class="n">26</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-8"><code data-line-number=" 8"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-8" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-8"></a><span class="n">Maximum</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">:</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-9"><code data-line-number=" 9"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-9" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-9"></a><span class="n">Minimum</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">:</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-10"><code data-line-number="10"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-10" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-10"></a><span class="n">StandardDeviation</span> <span class="p">:</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-11"><code data-line-number="11"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-11" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-11"></a><span class="n">Property</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">:</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-12"><code data-line-number="12"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-12" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-12"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-13"><code data-line-number="13"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-13" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-13"></a><span class="nb">PS </span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span><span class="p">&gt;</span> <span class="p">(</span><span class="nb">Get-ChildItem</span> <span class="n">-Filter</span> <span class="s1">&#39;*.txt&#39;</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-14"><code data-line-number="14"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-14" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-14"></a>&nbsp;&nbsp;<span class="k">ForEach</span><span class="n">-Object</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Length</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-15"><code data-line-number="15"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-15" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-15"></a>&nbsp;&nbsp;<span class="nb">Measure-Object</span> <span class="n">-Sum</span><span class="p">).</span><span class="n">Sum</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-16"><code data-line-number="16"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-16" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-16"></a><span class="n">26</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-17"><code data-line-number="17"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-17" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-17"></a><span class="nb">PS </span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span><span class="p">&gt;</span> <span class="p">(</span><span class="nb">Get-ChildItem</span> <span class="n">-Filter</span> <span class="s1">&#39;*.txt&#39;</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-18"><code data-line-number="18"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-18" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-18"></a>&nbsp;&nbsp;<span class="nb">Measure-Object</span> <span class="n">-Sum</span> <span class="n">-Property</span> <span class="n">Length</span><span class="p">).</span><span class="n">Sum</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-19"><code data-line-number="19"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-19" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-19"></a><span class="n">26</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-20"><code data-line-number="20"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-20" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-20"></a><span class="nb">PS </span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span><span class="p">&gt;</span> <span class="p">(</span><span class="nb">Get-ChildItem</span> <span class="n">-Recurse</span> <span class="n">-Filter</span> <span class="s1">&#39;*.txt&#39;</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-21"><code data-line-number="21"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-21" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-21"></a>&nbsp;&nbsp;<span class="nb">Measure-Object</span> <span class="n">-Sum</span> <span class="n">-Property</span> <span class="n">Length</span><span class="p">).</span><span class="n">Sum</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-22"><code data-line-number="22"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-22" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-22"></a><span class="n">30</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-23"><code data-line-number="23"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-23" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-23"></a><span class="nb">PS </span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span><span class="p">&gt;</span> <span class="nv">$measured</span> <span class="p">=</span> <span class="p">(</span><span class="nb">Get-ChildItem</span> <span class="n">-Recurse</span> <span class="n">-Filter</span> <span class="s1">&#39;*.txt&#39;</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-24"><code data-line-number="24"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-24" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-24"></a>&nbsp;&nbsp;<span class="nb">Measure-Object</span> <span class="n">-Sum</span> <span class="n">-Property</span> <span class="n">Length</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-25"><code data-line-number="25"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-25" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-25"></a><span class="nb">PS </span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">hello</span><span class="p">&gt;</span> <span class="nv">$measured</span><span class="p">.</span><span class="n">Sum</span> <span class="p">/</span> <span class="nv">$measured</span><span class="p">.</span><span class="n">Count</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-26"><code data-line-number="26"></code></a></td><td class="code"><code><a id="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-26" name="code_b6bfcaf6e4ff4bdba3718ce7e5233412217eab04-26"></a><span class="n">6</span>
</code></td></tr></table></div>
<p>We can iterate over all file objects, get their length (using <code>ForEach-Object</code> and a lambda), and then use <code>Measure-Object</code> to compute the sum (<code>Measure-Object</code> returns an object, we need to get its <code>Sum</code> property). We can replace the <code>ForEach-Object</code> call with the <code>-Property</code> argument in <code>Measure-Object</code>. And if we want to look into subdirectories, we can easily add <code>-Recurse</code> to <code>Get-ChildItem</code>. We get actual integers we can do math on.</p>
<p>You might have noticed I used <code>Get-ChildItem</code> instead of <code>dir</code> in the previous example. <code>Get-ChildItem</code> is the full name of the command (<em>cmdlet</em>). <code>dir</code> is one of its aliases, alongside <code>gci</code> and <code>ls</code> (Windows-only to avoid shadowing <code>/bin/ls</code>). Many common commands have aliases defined for easier typing and ease of use — <code>Copy-Item</code> can be written as <code>cp</code> (for compatibility with Unix), <code>copy</code> (for compatibility with MS-DOS), and <code>ci</code>. In our examples, we could also use <code>measure</code> for <code>Measure-Object</code> and <code>foreach</code> or <code>%</code> for <code>ForEach-Object</code>. Those aliases are a nice thing to have for interactive use, but for scripts, it’s best to use the full names for readability, and to avoid depending on the environment for those aliases.</p>
<h2 id="more-filesystem-operations">More filesystem operations</h2>
<h3 id="files-per-folder">Files per folder</h3>
<p>There’s a photo collection in a <code>Photos</code> folder, grouped into folders. The objective is to see how many <code>.jpg</code> files are in each folder. Here’s the PowerShell solution:</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_57265ffb293a3ee263d5186a87f7fc665c0e654a-1"><code data-line-number="1"></code></a></td><td class="code"><code><a id="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-1" name="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-1"></a><span class="nb">PS </span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">&gt;</span> <span class="nb">Get-ChildItem</span> <span class="n">Photos</span><span class="p">/*/*.</span><span class="n">jpg</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_57265ffb293a3ee263d5186a87f7fc665c0e654a-2"><code data-line-number="2"></code></a></td><td class="code"><code><a id="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-2" name="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-2"></a>&nbsp;&nbsp;<span class="nb">Group-Object</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Directory</span><span class="p">.</span><span class="n">Name</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_57265ffb293a3ee263d5186a87f7fc665c0e654a-3"><code data-line-number="3"></code></a></td><td class="code"><code><a id="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-3" name="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-3"></a>&nbsp;&nbsp;<span class="nb">Sort-Object</span> <span class="n">-Property</span> <span class="n">Count</span> <span class="n">-Descending</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_57265ffb293a3ee263d5186a87f7fc665c0e654a-4"><code data-line-number="4"></code></a></td><td class="code"><code><a id="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-4" name="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-4"></a><span class="n">Count</span> <span class="n">Name</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="nb">Group</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_57265ffb293a3ee263d5186a87f7fc665c0e654a-5"><code data-line-number="5"></code></a></td><td class="code"><code><a id="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-5" name="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-5"></a><span class="p">-----</span> <span class="p">----</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">-----</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_57265ffb293a3ee263d5186a87f7fc665c0e654a-6"><code data-line-number="6"></code></a></td><td class="code"><code><a id="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-6" name="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-6"></a>&nbsp;&nbsp;&nbsp;<span class="n">10</span> <span class="n">foo</span> <span class="n">bar</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">{</span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">Photos</span><span class="p">\</span><span class="n">foo</span> <span class="n">bar</span><span class="p">\</span><span class="n">img001</span><span class="p">.</span><span class="n">jpg</span><span class="p">,</span> <span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">Photos</span><span class="p">\</span><span class="n">foo</span> <span class="n">bar</span><span class="p">\</span><span class="n">img002</span><span class="p">.</span><span class="n">jpg</span><span class="p">,</span> <span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">Photos</span><span class="p">\</span><span class="n">foo</span> <span class="n">bar</span><span class="p">\</span><span class="n">img003</span><span class="p">.</span><span class="n">jpg</span><span class="err">…</span><span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_57265ffb293a3ee263d5186a87f7fc665c0e654a-7"><code data-line-number="7"></code></a></td><td class="code"><code><a id="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-7" name="code_57265ffb293a3ee263d5186a87f7fc665c0e654a-7"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">2</span> <span class="n">example</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">{</span><span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">Photos</span><span class="p">\</span><span class="n">example</span><span class="p">\</span><span class="n">img101</span><span class="p">.</span><span class="n">jpg</span><span class="p">,</span> <span class="n">C</span><span class="p">:\</span><span class="n">tmp</span><span class="p">\</span><span class="n">Photos</span><span class="p">\</span><span class="n">example</span><span class="p">\</span><span class="n">img201</span><span class="p">.</span><span class="n">jpg</span><span class="p">}</span>
</code></td></tr></table></div>
<p>In Unix land, <a href="https://stackoverflow.com/questions/15216370/how-to-count-number-of-files-in-each-directory">StackOverflow has a lot of solutions</a>. The top solution is <code>du -a | cut -d/ -f2 | sort | uniq -c | sort -nr</code> — a lot of tools mashed together, starting with a tool to check disk usage, and a lot of string manipulation. The second solution uses find, read, and shell globbing. The PowerShell solution is quite simple and obvious to anyone who has ever touched SQL.</p>
<p>The above example works for one level of nesting. For more levels, given <code>Photos\one\two\three.jpg</code>, use <code>Get-ChildItem -Filter '*.jpg' -Recurse Photos</code>, and:</p>
<ul>
<li>Group by <code>$_.Directory.Name</code> (same as before) to get <code>two</code></li>
<li>Group by <code>Split-Path -Parent ([System.IO.Path]::GetRelativePath(&quot;$PWD/Photos&quot;, $_.FullName))</code> to get <code>one/two</code></li>
<li>Group by <code>([System.IO.Path]::GetRelativePath(&quot;$PWD/Photos&quot;, $_.FullName)).Split([System.IO.Path]::DirectorySeparatorChar)[0]</code> to get <code>one</code></li>
</ul>
<p>(All of the above examples work for a single folder as well. The latter two examples don’t work on Windows PowerShell.)</p>
<h3 id="duplicate-finder">Duplicate finder</h3>
<p>Let’s build a simple tool to detect byte-for-byte duplicated files. <code>Get-FileHash</code> is a shell built-in. We can use <code>Group-Object</code> again, and <code>Where-Object</code> to filter only matching objects. Computing the hash of every file is quite inefficient, so we’ll group by the file length first, and then ensure the hashes match. This gives us a nice pipeline of 6 commands:</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-1"><code data-line-number=" 1"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-1" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-1"></a><span class="c"># Fully spelled out</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-2"><code data-line-number=" 2"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-2" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-2"></a><span class="nb">Get-ChildItem</span> <span class="n">-Recurse</span> <span class="o">-File</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-3"><code data-line-number=" 3"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-3" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-3"></a>&nbsp;&nbsp;<span class="nb">Group-Object</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Length</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-4"><code data-line-number=" 4"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-4" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-4"></a>&nbsp;&nbsp;<span class="nb">Where-Object</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Count</span> <span class="o">-gt</span> <span class="n">1</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-5"><code data-line-number=" 5"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-5" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-5"></a>&nbsp;&nbsp;<span class="k">ForEach</span><span class="n">-Object</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="nb">Group </span><span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-6"><code data-line-number=" 6"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-6" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-6"></a>&nbsp;&nbsp;<span class="nb">Group-Object</span> <span class="p">{</span> <span class="p">(</span><span class="nb">Get-FileHash</span> <span class="n">-Algorithm</span> <span class="n">MD5</span> <span class="nv">$_</span><span class="p">).</span><span class="n">Hash</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-7"><code data-line-number=" 7"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-7" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-7"></a>&nbsp;&nbsp;<span class="nb">Where-Object</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Count</span> <span class="o">-gt</span> <span class="n">1</span> <span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-8"><code data-line-number=" 8"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-8" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-8"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-9"><code data-line-number=" 9"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-9" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-9"></a><span class="c"># Using aliases</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-10"><code data-line-number="10"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-10" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-10"></a><span class="nb">gci </span><span class="n">-Recurse</span> <span class="o">-File</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-11"><code data-line-number="11"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-11" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-11"></a>&nbsp;&nbsp;<span class="nb">group </span><span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Length</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-12"><code data-line-number="12"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-12" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-12"></a>&nbsp;&nbsp;<span class="nb">where </span><span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Count</span> <span class="o">-gt</span> <span class="n">1</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-13"><code data-line-number="13"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-13" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-13"></a>&nbsp;&nbsp;<span class="k">foreach</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="nb">Group </span><span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-14"><code data-line-number="14"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-14" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-14"></a>&nbsp;&nbsp;<span class="nb">group </span><span class="p">{</span> <span class="p">(</span><span class="nb">Get-FileHash</span> <span class="n">-Algorithm</span> <span class="n">MD5</span> <span class="nv">$_</span><span class="p">).</span><span class="n">Hash</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-15"><code data-line-number="15"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-15" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-15"></a>&nbsp;&nbsp;<span class="nb">where </span><span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Count</span> <span class="o">-gt</span> <span class="n">1</span> <span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-16"><code data-line-number="16"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-16" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-16"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-17"><code data-line-number="17"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-17" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-17"></a><span class="c"># Using less readable aliases</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-18"><code data-line-number="18"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-18" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-18"></a><span class="nb">gci </span><span class="n">-Recurse</span> <span class="o">-File</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-19"><code data-line-number="19"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-19" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-19"></a>&nbsp;&nbsp;<span class="nb">group </span><span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Length</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-20"><code data-line-number="20"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-20" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-20"></a>&nbsp;&nbsp;<span class="p">?</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Count</span> <span class="o">-gt</span> <span class="n">1</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-21"><code data-line-number="21"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-21" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-21"></a>&nbsp;&nbsp;<span class="p">%</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="nb">Group </span><span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-22"><code data-line-number="22"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-22" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-22"></a>&nbsp;&nbsp;<span class="nb">group </span><span class="p">{</span> <span class="p">(</span><span class="nb">Get-FileHash</span> <span class="n">-Algorithm</span> <span class="n">MD5</span> <span class="nv">$_</span><span class="p">).</span><span class="n">Hash</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_23a175a4070286ab72d28ef04e77af82705e2c7c-23"><code data-line-number="23"></code></a></td><td class="code"><code><a id="code_23a175a4070286ab72d28ef04e77af82705e2c7c-23" name="code_23a175a4070286ab72d28ef04e77af82705e2c7c-23"></a>&nbsp;&nbsp;<span class="p">?</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Count</span> <span class="o">-gt</span> <span class="n">1</span> <span class="p">}</span>
</code></td></tr></table></div>
<h2 id="serious-scripting-software-bill-of-materials">Serious Scripting: Software Bill of Materials</h2>
<p>Software Bills of Materials (SBOMs) and supply chain security are all the rage these days. The boss wants to have something like that, i.e. a CSV file with a list of packages and versions, and only the direct production dependencies. Sure, there exist standards like SPDX, but the boss does not like those pesky “standards”. The backend is written in C#, and the frontend is written in Node.js. Since we care only about the production dependencies, we can look at the <code>.csproj</code> and <code>package.json</code> files. For Node packages, we’ll also try to fetch the license name from the npm API (the API is a bit more complicated for NuGet, so we’ll keep it as a <code>TODO</code> in this example).</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-1"><code data-line-number=" 1"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-1" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-1"></a><span class="nv">$ErrorActionPreference</span> <span class="p">=</span> <span class="s2">&quot;Stop&quot;</span> <span class="c"># stop execution on any error</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-2"><code data-line-number=" 2"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-2" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-2"></a><span class="nb">Set-StrictMode</span> <span class="n">-Version</span> <span class="n">3</span><span class="p">.</span><span class="n">0</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-3"><code data-line-number=" 3"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-3" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-3"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-4"><code data-line-number=" 4"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-4" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-4"></a><span class="k">function</span> <span class="nb">Get-CsprojPackages</span><span class="p">(</span><span class="no">[string]</span><span class="nv">$Path</span><span class="p">)</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-5"><code data-line-number=" 5"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-5" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-5"></a>&nbsp;&nbsp;<span class="k">return</span> <span class="nb">Select-Xml</span> <span class="n">-Path</span> <span class="nv">$Path</span> <span class="n">-XPath</span> <span class="s1">&#39;//PackageReference&#39;</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-6"><code data-line-number=" 6"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-6" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-6"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">ForEach</span><span class="n">-Object</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-7"><code data-line-number=" 7"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-7" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-7"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="no">[PSCustomObject]</span><span class="p">@{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-8"><code data-line-number=" 8"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-8" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-8"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">Name</span> <span class="p">=</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Node</span><span class="p">.</span><span class="n">GetAttribute</span><span class="p">(</span><span class="s2">&quot;Include&quot;</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-9"><code data-line-number=" 9"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-9" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-9"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">Version</span> <span class="p">=</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Node</span><span class="p">.</span><span class="n">GetAttribute</span><span class="p">(</span><span class="s2">&quot;Version&quot;</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-10"><code data-line-number="10"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-10" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-10"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">Source</span> <span class="p">=</span> <span class="s1">&#39;nuget&#39;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-11"><code data-line-number="11"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-11" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-11"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">License</span> <span class="p">=</span> <span class="s1">&#39;TODO&#39;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-12"><code data-line-number="12"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-12" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-12"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-13"><code data-line-number="13"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-13" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-13"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-14"><code data-line-number="14"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-14" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-14"></a><span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-15"><code data-line-number="15"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-15" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-15"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-16"><code data-line-number="16"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-16" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-16"></a><span class="k">function</span> <span class="nb">Get-NodePackages</span><span class="p">(</span><span class="no">[string]</span><span class="nv">$Path</span><span class="p">)</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-17"><code data-line-number="17"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-17" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-17"></a>&nbsp;&nbsp;<span class="nv">$nameToVersion</span> <span class="p">=</span> <span class="p">(</span><span class="nb">Get-Content</span> <span class="n">-Raw</span> <span class="nv">$Path</span> <span class="p">|</span> <span class="nb">ConvertFrom-Json</span><span class="p">).</span><span class="n">dependencies</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-18"><code data-line-number="18"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-18" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-18"></a>&nbsp;&nbsp;<span class="k">return</span> <span class="nv">$nameToVersion</span><span class="p">.</span><span class="n">psobject</span><span class="p">.</span><span class="n">Properties</span> <span class="p">|</span> <span class="k">ForEach</span><span class="n">-Object</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-19"><code data-line-number="19"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-19" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-19"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="no">[PSCustomObject]</span><span class="p">@{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-20"><code data-line-number="20"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-20" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-20"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">Name</span> <span class="p">=</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Name</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-21"><code data-line-number="21"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-21" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-21"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">Version</span> <span class="p">=</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Value</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-22"><code data-line-number="22"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-22" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-22"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">Source</span> <span class="p">=</span> <span class="s1">&#39;node&#39;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-23"><code data-line-number="23"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-23" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-23"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">License</span> <span class="p">=</span> <span class="p">(</span><span class="nb">Get-NodeLicense</span> <span class="n">-Name</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-24"><code data-line-number="24"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-24" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-24"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-25"><code data-line-number="25"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-25" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-25"></a>&nbsp;&nbsp;<span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-26"><code data-line-number="26"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-26" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-26"></a><span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-27"><code data-line-number="27"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-27" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-27"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-28"><code data-line-number="28"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-28" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-28"></a><span class="k">function</span> <span class="nb">Get-NodeLicense</span><span class="p">(</span><span class="no">[string]</span><span class="nv">$Name</span><span class="p">)</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-29"><code data-line-number="29"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-29" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-29"></a>&nbsp;&nbsp;<span class="k">try</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-30"><code data-line-number="30"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-30" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-30"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="p">(</span><span class="nb">Invoke-RestMethod</span> <span class="n">-TimeoutSec</span> <span class="n">3</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-31"><code data-line-number="31"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-31" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-31"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="s2">&quot;https://registry.npmjs.org/$Name&quot;</span><span class="p">).</span><span class="n">license</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-32"><code data-line-number="32"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-32" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-32"></a>&nbsp;&nbsp;<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-33"><code data-line-number="33"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-33" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-33"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="s2">&quot;???&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-34"><code data-line-number="34"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-34" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-34"></a>&nbsp;&nbsp;<span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-35"><code data-line-number="35"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-35" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-35"></a><span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-36"><code data-line-number="36"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-36" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-36"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-37"><code data-line-number="37"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-37" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-37"></a><span class="nv">$csprojData</span> <span class="p">=</span> <span class="p">@(</span><span class="nb">Get-ChildItem</span> <span class="n">-Recurse</span> <span class="n">-Filter</span> <span class="s1">&#39;*.csproj&#39;</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-38"><code data-line-number="38"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-38" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-38"></a>&nbsp;&nbsp;<span class="k">ForEach</span><span class="n">-Object</span> <span class="p">{</span> <span class="nb">Get-CsprojPackages</span> <span class="nv">$_</span><span class="p">.</span><span class="n">FullName</span> <span class="p">})</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-39"><code data-line-number="39"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-39" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-39"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-40"><code data-line-number="40"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-40" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-40"></a><span class="nv">$nodeData</span> <span class="p">=</span> <span class="p">@(</span><span class="nb">Get-ChildItem</span> <span class="n">-Recurse</span> <span class="n">-Filter</span> <span class="s1">&#39;package.json&#39;</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-41"><code data-line-number="41"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-41" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-41"></a>&nbsp;&nbsp;<span class="nb">Where-Object</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">FullName</span> <span class="o">-notlike</span> <span class="s1">&#39;*node_modules*&#39;</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-42"><code data-line-number="42"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-42" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-42"></a>&nbsp;&nbsp;<span class="k">ForEach</span><span class="n">-Object</span> <span class="p">{</span> <span class="nb">Get-NodePackages</span> <span class="nv">$_</span><span class="p">.</span><span class="n">FullName</span> <span class="p">})</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-43"><code data-line-number="43"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-43" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-43"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-44"><code data-line-number="44"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-44" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-44"></a><span class="nv">$allData</span> <span class="p">=</span> <span class="nv">$csProjData</span> <span class="p">+</span> <span class="nv">$nodeData</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-45"><code data-line-number="45"></code></a></td><td class="code"><code><a id="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-45" name="code_4fe48e7fd8d202d89c2e576d8d8b41ace059bc6a-45"></a><span class="nv">$allData</span> <span class="p">|</span> <span class="nb">ConvertTo-Csv</span> <span class="n">-NoTypeInformation</span> <span class="p">|</span> <span class="nb">Tee-Object</span> <span class="n">sbom</span><span class="p">.</span><span class="n">csv</span>
</code></td></tr></table></div>
<p>Just like every well-written shell script starts with <code>set -euo pipefail</code>, every PowerShell script should start with <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/set-strictmode?view=powershell-7.4"><code>$ErrorActionPreference = &quot;Stop&quot;</code></a> so that execution is stopped as soon as something goes wrong. Note that this does <em>not</em> affect native commands, you still need to check <code>$LASTEXITCODE</code>. Another useful early command is <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/set-strictmode?view=powershell-7.4"><code>Set-StrictMode -Version 3.0</code></a> to catch undefined variables.</p>
<p>For <code>.csproj</code> files, which are XML, we look for <code>PackageReference</code> elements using XPath, and then build a PSCustomObject out of a hashmap — extracting the appropriate attributes from the <code>PackageReference</code> nodes.</p>
<p>For <code>package.json</code>, we read the file, parse the JSON, and extract the properties of the <code>dependencies</code> object (it’s a map of package names to versions). To get the license, we use <code>Invoke-RestMethod</code>, which takes care of parsing JSON for us.</p>
<p>In the main body of the script, we look for the appropriate files (skipping things under <code>node_modules</code>) and call our parser functions. After retrieving all data, we concatenate the two arrays, convert to CSV, and use <code>Tee-Object</code> to output to a file and to standard output. We get this:</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-1"><code data-line-number=" 1"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-1" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-1"></a><span class="s">&quot;Name&quot;</span><span class="p">,</span><span class="s">&quot;Version&quot;</span><span class="p">,</span><span class="s">&quot;Source&quot;</span><span class="p">,</span><span class="s">&quot;License&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-2"><code data-line-number=" 2"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-2" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-2"></a><span class="s">&quot;AWSSDK.S3&quot;</span><span class="p">,</span><span class="s">&quot;3.7.307.24&quot;</span><span class="p">,</span><span class="s">&quot;nuget&quot;</span><span class="p">,</span><span class="s">&quot;TODO&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-3"><code data-line-number=" 3"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-3" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-3"></a><span class="s">&quot;Microsoft.AspNetCore.SpaProxy&quot;</span><span class="p">,</span><span class="s">&quot;7.0.17&quot;</span><span class="p">,</span><span class="s">&quot;nuget&quot;</span><span class="p">,</span><span class="s">&quot;TODO&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-4"><code data-line-number=" 4"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-4" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-4"></a><span class="s">&quot;@testing-library/jest-dom&quot;</span><span class="p">,</span><span class="s">&quot;^5.17.0&quot;</span><span class="p">,</span><span class="s">&quot;node&quot;</span><span class="p">,</span><span class="s">&quot;MIT&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-5"><code data-line-number=" 5"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-5" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-5"></a><span class="s">&quot;@testing-library/react&quot;</span><span class="p">,</span><span class="s">&quot;^13.4.0&quot;</span><span class="p">,</span><span class="s">&quot;node&quot;</span><span class="p">,</span><span class="s">&quot;MIT&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-6"><code data-line-number=" 6"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-6" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-6"></a><span class="s">&quot;@testing-library/user-event&quot;</span><span class="p">,</span><span class="s">&quot;^13.5.0&quot;</span><span class="p">,</span><span class="s">&quot;node&quot;</span><span class="p">,</span><span class="s">&quot;MIT&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-7"><code data-line-number=" 7"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-7" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-7"></a><span class="s">&quot;@types/jest&quot;</span><span class="p">,</span><span class="s">&quot;^27.5.2&quot;</span><span class="p">,</span><span class="s">&quot;node&quot;</span><span class="p">,</span><span class="s">&quot;MIT&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-8"><code data-line-number=" 8"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-8" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-8"></a><span class="s">&quot;@types/node&quot;</span><span class="p">,</span><span class="s">&quot;^16.18.96&quot;</span><span class="p">,</span><span class="s">&quot;node&quot;</span><span class="p">,</span><span class="s">&quot;MIT&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-9"><code data-line-number=" 9"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-9" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-9"></a><span class="s">&quot;@types/react&quot;</span><span class="p">,</span><span class="s">&quot;^18.3.1&quot;</span><span class="p">,</span><span class="s">&quot;node&quot;</span><span class="p">,</span><span class="s">&quot;MIT&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-10"><code data-line-number="10"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-10" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-10"></a><span class="s">&quot;@types/react-dom&quot;</span><span class="p">,</span><span class="s">&quot;^18.3.0&quot;</span><span class="p">,</span><span class="s">&quot;node&quot;</span><span class="p">,</span><span class="s">&quot;MIT&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-11"><code data-line-number="11"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-11" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-11"></a><span class="s">&quot;react&quot;</span><span class="p">,</span><span class="s">&quot;^18.3.1&quot;</span><span class="p">,</span><span class="s">&quot;node&quot;</span><span class="p">,</span><span class="s">&quot;MIT&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-12"><code data-line-number="12"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-12" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-12"></a><span class="s">&quot;react-dom&quot;</span><span class="p">,</span><span class="s">&quot;^18.3.1&quot;</span><span class="p">,</span><span class="s">&quot;node&quot;</span><span class="p">,</span><span class="s">&quot;MIT&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-13"><code data-line-number="13"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-13" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-13"></a><span class="s">&quot;react-scripts&quot;</span><span class="p">,</span><span class="s">&quot;5.0.1&quot;</span><span class="p">,</span><span class="s">&quot;node&quot;</span><span class="p">,</span><span class="s">&quot;MIT&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-14"><code data-line-number="14"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-14" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-14"></a><span class="s">&quot;typescript&quot;</span><span class="p">,</span><span class="s">&quot;^4.9.5&quot;</span><span class="p">,</span><span class="s">&quot;node&quot;</span><span class="p">,</span><span class="s">&quot;Apache-2.0&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_7e45a17d671643979b2466b48fce4eb30fc60fa8-15"><code data-line-number="15"></code></a></td><td class="code"><code><a id="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-15" name="code_7e45a17d671643979b2466b48fce4eb30fc60fa8-15"></a><span class="s">&quot;web-vitals&quot;</span><span class="p">,</span><span class="s">&quot;^2.1.4&quot;</span><span class="p">,</span><span class="s">&quot;node&quot;</span><span class="p">,</span><span class="s">&quot;Apache-2.0&quot;</span>
</code></td></tr></table></div>
<p>Could it be done in a different language? Certainly, but PowerShell is really easy to integrate with CI, e.g. <a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#example-running-a-command-using-powershell-core">GitHub Actions</a> or <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/powershell-v2?view=azure-pipelines">Azure Pipelines</a>. On Linux, you might be tempted to use Python — and you could get something done equally simply, as long as you don’t mind using the ugly <code>urllib.request</code> library, or alternatively ensuring <code>requests</code> is installed (and then you get into the hell that is Python package management).</p>
<h2 id="using.net-classes">Using .NET classes</h2>
<p>PowerShell is built on top of .NET. This isn’t just the implementation technology — PowerShell gives access to everything the .NET standard library offers. For example, the alternate ways to group photos in multiple subdirectories we’ve explored above involve a call to a static method of the .NET <code>System.IO.Path</code> class.</p>
<p>Other .NET types are also available. Need a HashSet? Here goes:</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-1"><code data-line-number=" 1"></code></a></td><td class="code"><code><a id="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-1" name="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-1"></a><span class="n">PS</span><span class="p">&gt;</span> <span class="nv">$set</span> <span class="p">=</span> <span class="nb">New-Object</span> <span class="n">System</span><span class="p">.</span><span class="n">Collections</span><span class="p">.</span><span class="n">Generic</span><span class="p">.</span><span class="n">HashSet</span><span class="no">[string]</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-2"><code data-line-number=" 2"></code></a></td><td class="code"><code><a id="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-2" name="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-2"></a><span class="n">PS</span><span class="p">&gt;</span> <span class="nv">$set</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="s2">&quot;hello&quot;</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-3"><code data-line-number=" 3"></code></a></td><td class="code"><code><a id="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-3" name="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-3"></a><span class="n">True</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-4"><code data-line-number=" 4"></code></a></td><td class="code"><code><a id="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-4" name="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-4"></a><span class="n">PS</span><span class="p">&gt;</span> <span class="nv">$set</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="s2">&quot;hello&quot;</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-5"><code data-line-number=" 5"></code></a></td><td class="code"><code><a id="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-5" name="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-5"></a><span class="n">False</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-6"><code data-line-number=" 6"></code></a></td><td class="code"><code><a id="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-6" name="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-6"></a><span class="n">PS</span><span class="p">&gt;</span> <span class="nv">$set</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="s2">&quot;world&quot;</span><span class="p">)</span> <span class="p">|</span> <span class="nb">Out-Null</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-7"><code data-line-number=" 7"></code></a></td><td class="code"><code><a id="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-7" name="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-7"></a><span class="n">PS</span><span class="p">&gt;</span> <span class="nv">$set</span><span class="p">.</span><span class="n">Count</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-8"><code data-line-number=" 8"></code></a></td><td class="code"><code><a id="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-8" name="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-8"></a><span class="n">2</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-9"><code data-line-number=" 9"></code></a></td><td class="code"><code><a id="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-9" name="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-9"></a><span class="n">PS</span><span class="p">&gt;</span> <span class="nv">$set</span> <span class="o">-contains</span> <span class="s2">&quot;hello&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-10"><code data-line-number="10"></code></a></td><td class="code"><code><a id="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-10" name="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-10"></a><span class="n">True</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-11"><code data-line-number="11"></code></a></td><td class="code"><code><a id="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-11" name="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-11"></a><span class="n">PS</span><span class="p">&gt;</span> <span class="nv">$set</span> <span class="o">-contains</span> <span class="s2">&quot;world&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-12"><code data-line-number="12"></code></a></td><td class="code"><code><a id="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-12" name="code_a55619a6cf811c2b6ab2c2bf17dbd8f62082087f-12"></a><span class="n">False</span>
</code></td></tr></table></div>
<p>It is also possible to load any .NET DLL into PowerShell (as long as it’s compatible with the .NET version PowerShell is built against) and use it as usual from C# (although possibly with slightly ugly syntax).</p>
<h2 id="sick-windows-tricks">Sick Windows Tricks</h2>
<p>Microsoft supposedly killed off Internet Explorer last year. Attempting to launch <code>iexplore.exe</code> will bring up Microsoft Edge. But you see, Internet Explorer is a crucial part of Windows, and has been so for over two decades. Software vendors have built software that depends on IE being there and being able to show web content. Some of them are using web views, but some of them prefer something else: COM.</p>
<p>COM, or Component Object Model, is Microsoft’s thing for interoperability between different applications and/or components. COM is basically a way for classes offered by different vendors and potentially written in different languages to talk to one another. Under the hood, COM is C++ <code>vtable</code>s plus standard reference counting and class loading/discovery mechanisms. The .NET Framework, and its successor .NET, have always included COM interoperability. The modern WinRT platform is COM on steroids.</p>
<p>Coming back to Internet Explorer, it exposes some COM classes. They were <em>not</em> removed with <code>iexplore.exe</code>. This means you can bring up a regular Internet Explorer window in just two lines of PowerShell:</p>
<div class="highlight"><pre><span></span><span class="nv">$ie</span> <span class="p">=</span> <span class="nb">New-Object</span> <span class="n">-ComObject</span> <span class="n">InternetExplorer</span><span class="p">.</span><span class="n">Application</span>
<span class="nv">$ie</span><span class="p">.</span><span class="n">Visible</span> <span class="p">=</span> <span class="nv">$true</span>
</pre></div>

<p>Why would you do that? The <code>InternetExplorer.Application</code> object lets you control the browser, e.g. you can use <code>$ie.Navigate(&quot;https://example.com/&quot;)</code> to go to a page. Why would you want to launch IE in 2024? I don’t know, I guess you can use it to laugh in the faces of the Microsoft developers who removed the user-accessible shortcuts? But there definitely exist some legacy applications that expect a COM-controllable IE.</p>
<p>We have already explored the possibility of using classes from .NET. .NET comes with a GUI framework named Windows Forms, <a href="https://learn.microsoft.com/en-us/powershell/scripting/samples/creating-a-custom-input-box?view=powershell-7.4">which can be loaded from PowerShell and used to build a GUI.</a> There is no form designer, so it requires manually defining and positioning controls, but it actually works.</p>
<p>PowerShell can also do various Windows management tasks. It can manage boot settings, BitLocker, Hyper-V, networking, storage… For example, to get the percentage of disk space remaining:</p>
<div class="highlight"><pre><span></span><span class="nv">$c</span> <span class="p">=</span> <span class="nb">Get-Volume</span> <span class="n">C</span>
<span class="s2">&quot;</span><span class="p">$((</span><span class="nv">$c</span><span class="p">.</span><span class="n">SizeRemaining</span> <span class="p">/</span> <span class="nv">$c</span><span class="p">.</span><span class="n">Size</span><span class="p">)</span> <span class="p">*</span> <span class="n">100</span><span class="p">)</span><span class="s2">%&quot;</span>
</pre></div>

<h2 id="getting-out-of-powershell-land">Getting out of PowerShell land</h2>
<p>As a shell, PowerShell can obviously launch subprocesses. Unlike something like Python, running a subprocess is as simple as running anything else. If you need to <code>git pull</code>, you just type that. Or you can make PowerShell interact with non-PowerShell commands, reading output and passing arguments:</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-1"><code data-line-number=" 1"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-1" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-1"></a><span class="nv">$changes</span> <span class="p">=</span> <span class="p">(</span><span class="n">git</span> <span class="n">status</span> <span class="p">-</span><span class="n">-porcelain</span> <span class="p">-</span><span class="n">-null</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-2"><code data-line-number=" 2"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-2" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-2"></a><span class="k">if</span> <span class="p">(</span><span class="nv">$LASTEXITCODE</span> <span class="o">-eq</span> <span class="n">128</span><span class="p">)</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-3"><code data-line-number=" 3"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-3" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-3"></a>&nbsp;&nbsp;<span class="k">throw</span> <span class="s2">&quot;Not a git repository&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-4"><code data-line-number=" 4"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-4" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-4"></a><span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nv">$LASTEXITCODE</span> <span class="o">-ne</span> <span class="n">0</span><span class="p">)</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-5"><code data-line-number=" 5"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-5" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-5"></a>&nbsp;&nbsp;<span class="k">throw</span> <span class="s2">&quot;Getting changes from git failed&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-6"><code data-line-number=" 6"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-6" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-6"></a><span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-7"><code data-line-number=" 7"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-7" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-7"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-8"><code data-line-number=" 8"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-8" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-8"></a><span class="k">if</span> <span class="p">(</span><span class="nv">$null</span> <span class="o">-eq</span> <span class="nv">$changes</span><span class="p">)</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-9"><code data-line-number=" 9"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-9" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-9"></a>&nbsp;&nbsp;<span class="nb">Write-Host</span> <span class="s2">&quot;No changes found&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-10"><code data-line-number="10"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-10" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-10"></a><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-11"><code data-line-number="11"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-11" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-11"></a>&nbsp;&nbsp;<span class="nv">$untrackedFiles</span> <span class="p">=</span> <span class="p">@(</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-12"><code data-line-number="12"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-12" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-12"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="nv">$changes</span><span class="p">.</span><span class="n">Split</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">`0</span><span class="s2">&quot;</span><span class="p">)</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-13"><code data-line-number="13"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-13" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-13"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="nb">Where-Object</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">StartsWith</span><span class="p">(</span><span class="s1">&#39;?? &#39;</span><span class="p">)</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-14"><code data-line-number="14"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-14" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-14"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">ForEach</span><span class="n">-Object</span> <span class="p">{</span> <span class="nv">$_</span><span class="p">.</span><span class="n">Remove</span><span class="p">(</span><span class="n">0</span><span class="p">,</span> <span class="n">3</span><span class="p">)</span> <span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-15"><code data-line-number="15"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-15" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-15"></a>&nbsp;&nbsp;<span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-16"><code data-line-number="16"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-16" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-16"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-17"><code data-line-number="17"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-17" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-17"></a>&nbsp;&nbsp;<span class="c"># Alternate spelling for regex fans:</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-18"><code data-line-number="18"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-18" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-18"></a>&nbsp;&nbsp;<span class="nv">$untrackedFilesForRegexFans</span> <span class="p">=</span> <span class="p">@(</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-19"><code data-line-number="19"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-19" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-19"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="nv">$changes</span><span class="p">.</span><span class="n">Split</span><span class="p">(</span><span class="s2">&quot;</span><span class="se">`0</span><span class="s2">&quot;</span><span class="p">)</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-20"><code data-line-number="20"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-20" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-20"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="nb">Where-Object</span> <span class="p">{</span> <span class="nv">$_</span> <span class="o">-match</span> <span class="s1">&#39;^\?\? &#39;</span> <span class="p">}</span> <span class="p">|</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-21"><code data-line-number="21"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-21" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-21"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">ForEach</span><span class="n">-Object</span> <span class="p">{</span> <span class="nv">$_</span> <span class="o">-replace</span> <span class="s1">&#39;^\?\? &#39;</span><span class="p">,</span><span class="s1">&#39;&#39;</span> <span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-22"><code data-line-number="22"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-22" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-22"></a>&nbsp;&nbsp;<span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-23"><code data-line-number="23"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-23" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-23"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-24"><code data-line-number="24"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-24" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-24"></a>&nbsp;&nbsp;<span class="k">if</span> <span class="p">(</span><span class="nv">$untrackedFiles</span><span class="p">)</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-25"><code data-line-number="25"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-25" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-25"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="nb">Write-Host</span> <span class="s2">&quot;Opening </span><span class="p">$(</span><span class="nv">$untrackedFiles</span><span class="p">.</span><span class="n">Length</span><span class="p">)</span><span class="s2"> untracked files in VS Code&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-26"><code data-line-number="26"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-26" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-26"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">code</span> <span class="nv">$untrackedFiles</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-27"><code data-line-number="27"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-27" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-27"></a>&nbsp;&nbsp;<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-28"><code data-line-number="28"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-28" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-28"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="nb">Write-Host</span> <span class="s2">&quot;No untracked files&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-29"><code data-line-number="29"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-29" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-29"></a>&nbsp;&nbsp;<span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-30"><code data-line-number="30"></code></a></td><td class="code"><code><a id="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-30" name="code_2fac3f71ae9cdda9430242393e19be0d1318b0bf-30"></a><span class="p">}</span>
</code></td></tr></table></div>
<p>I chose to compute untracked files with the help of standard .NET string manipulation methods, but there’s also a regex option. On a related note, there are three content check operators: <code>-match</code> uses regex, <code>-like</code> uses <a href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_wildcards?view=powershell-7.4">wildcards</a>, and <code>-contains</code> checks collection membership.</p>
<h2 id="profile-script">Profile script</h2>
<p>I use a fairly small profile script that adds some behaviours I’m used to from Unix, and to make Tab completion show a menu. Here are the most basic bits:</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_774bd000262a6b91d868ee5c7d4e2939b4716853-1"><code data-line-number=" 1"></code></a></td><td class="code"><code><a id="code_774bd000262a6b91d868ee5c7d4e2939b4716853-1" name="code_774bd000262a6b91d868ee5c7d4e2939b4716853-1"></a><span class="nb">Set-PSReadLineOption</span> <span class="n">-HistorySearchCursorMovesToEnd</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_774bd000262a6b91d868ee5c7d4e2939b4716853-2"><code data-line-number=" 2"></code></a></td><td class="code"><code><a id="code_774bd000262a6b91d868ee5c7d4e2939b4716853-2" name="code_774bd000262a6b91d868ee5c7d4e2939b4716853-2"></a><span class="nb">Set-PSReadLineKeyHandler</span> <span class="n">-Key</span> <span class="n">UpArrow</span> <span class="n">-Function</span> <span class="n">HistorySearchBackward</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_774bd000262a6b91d868ee5c7d4e2939b4716853-3"><code data-line-number=" 3"></code></a></td><td class="code"><code><a id="code_774bd000262a6b91d868ee5c7d4e2939b4716853-3" name="code_774bd000262a6b91d868ee5c7d4e2939b4716853-3"></a><span class="nb">Set-PSReadLineKeyHandler</span> <span class="n">-Key</span> <span class="n">DownArrow</span> <span class="n">-Function</span> <span class="n">HistorySearchForward</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_774bd000262a6b91d868ee5c7d4e2939b4716853-4"><code data-line-number=" 4"></code></a></td><td class="code"><code><a id="code_774bd000262a6b91d868ee5c7d4e2939b4716853-4" name="code_774bd000262a6b91d868ee5c7d4e2939b4716853-4"></a><span class="nb">Set-PSReadlineKeyHandler</span> <span class="n">-Key</span> <span class="n">ctrl</span><span class="p">+</span><span class="n">d</span> <span class="n">-Function</span> <span class="n">DeleteCharOrExit</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_774bd000262a6b91d868ee5c7d4e2939b4716853-5"><code data-line-number=" 5"></code></a></td><td class="code"><code><a id="code_774bd000262a6b91d868ee5c7d4e2939b4716853-5" name="code_774bd000262a6b91d868ee5c7d4e2939b4716853-5"></a><span class="nb">Set-PSReadlineKeyHandler</span> <span class="n">-Key</span> <span class="n">Tab</span> <span class="n">-Function</span> <span class="n">MenuComplete</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_774bd000262a6b91d868ee5c7d4e2939b4716853-6"><code data-line-number=" 6"></code></a></td><td class="code"><code><a id="code_774bd000262a6b91d868ee5c7d4e2939b4716853-6" name="code_774bd000262a6b91d868ee5c7d4e2939b4716853-6"></a><span class="nb">Set-PSReadLineOption</span> <span class="n">-AddToHistoryHandler</span> <span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_774bd000262a6b91d868ee5c7d4e2939b4716853-7"><code data-line-number=" 7"></code></a></td><td class="code"><code><a id="code_774bd000262a6b91d868ee5c7d4e2939b4716853-7" name="code_774bd000262a6b91d868ee5c7d4e2939b4716853-7"></a>&nbsp;&nbsp;<span class="k">param</span><span class="p">(</span><span class="nv">$command</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_774bd000262a6b91d868ee5c7d4e2939b4716853-8"><code data-line-number=" 8"></code></a></td><td class="code"><code><a id="code_774bd000262a6b91d868ee5c7d4e2939b4716853-8" name="code_774bd000262a6b91d868ee5c7d4e2939b4716853-8"></a>&nbsp;&nbsp;<span class="c"># Commands starting with space are not remembered.</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_774bd000262a6b91d868ee5c7d4e2939b4716853-9"><code data-line-number=" 9"></code></a></td><td class="code"><code><a id="code_774bd000262a6b91d868ee5c7d4e2939b4716853-9" name="code_774bd000262a6b91d868ee5c7d4e2939b4716853-9"></a>&nbsp;&nbsp;<span class="k">return</span> <span class="o">-not</span> <span class="p">(</span><span class="nv">$command</span> <span class="o">-like</span> <span class="s1">&#39; *&#39;</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2024/04/29/powershell-the-object-oriented-shell-you-didnt-know-you-needed/#code_774bd000262a6b91d868ee5c7d4e2939b4716853-10"><code data-line-number="10"></code></a></td><td class="code"><code><a id="code_774bd000262a6b91d868ee5c7d4e2939b4716853-10" name="code_774bd000262a6b91d868ee5c7d4e2939b4716853-10"></a><span class="p">}</span>
</code></td></tr></table></div>
<p>Apart from that, I use a few aliases and a pretty prompt with the help of <a href="https://ohmyposh.dev/">oh-my-posh</a>.</p>
<h2 id="the-unusual-and-sometimes-confusing-parts">The unusual and sometimes confusing parts</h2>
<p>PowerShell can be verbose. Some of its syntax is a little quirky, compared to other languages, e.g. the equality and logic operators (for example, <code>-eq</code>, <code>-le</code>, <code>-and</code>). The aliases usually help with remembering commands, but they can’t always be depended on — <code>ls</code> is defined as an alias only on Windows, and Windows PowerShell aliases <code>wget</code> and <code>curl</code> to <code>Invoke-WebRequest</code>, even though all three have completely different command line arguments and outputs (this was removed in PowerShell).</p>
<p>Moreover, the Unix/DOS aliases do not change the argument handling. <code>rm -rf foo</code> is invalid. <code>rm -r foo</code> is, since argument names can be abbreviated as long as the abbreviation is unambiguous. <code>rm -r -f foo</code> is not valid, because <code>-f</code> can be an abbreviation of <code>-Filter</code> or <code>-Force</code> (so <code>rm -r -fo foo</code>) will do. <code>rm foo bar</code> does not work, an array is needed: <code>rm foo,bar</code>.</p>
<p><code>C:\Windows\regedit.exe</code> launches the Registry editor. <code>&quot;C:\Program Files\Mozilla Firefox\firefox.exe&quot;</code> is a string. Launching something with spaces in its name requires the call operator: <code>&amp; &quot;C:\Program Files\Mozilla Firefox\firefox.exe&quot;</code>. PowerShell’s tab completion will add the <code>&amp;</code> if necessary.</p>
<p>There are two function call syntaxes. Calling a function/cmdlet uses the shell-style syntax with argument names: <code>Some-Function -Arg1 value1 -Arg2 value2</code>, and argument names can be abbreviated, and can sometimes be omitted. Calling a method requires a more traditional syntax: <code>$obj.SomeMethod(value1, value2)</code>. Names are case-insensitive in either case.</p>
<p>The escape character is the backtick. The backslash is the path separator in Windows, so making it an escape character would make everything painful on Windows. At least it makes it easy to write regex.</p>
<h2 id="the-ugliest-part">The ugliest part</h2>
<p>The ugliest and the least intuitive part of PowerShell is the handling of single-element arrays. PowerShell <em>really</em> wants to unpack them to a scalar. The command <code>(Get-ChildItem).Length</code> will produce the number of files in the current directory — <em>unless</em> there is exactly one file, in which case it will produce the single file’s size in bytes. And if there are zero items, instead of an empty array, PowerShell produces <code>$null</code>. Sometimes, things will work out in the end (since many cmdlets are happy to get either as inputs), but sometimes, PowerShell must be asked to stop this madness and return an array: <code>@(Get-ChildItem).Length</code>.</p>
<p>The previous example with <code>git status</code> leverages its <code>--null</code> argument to get zero-delimited data, so we expect either <code>$null</code> or a single string according to the rules. If we didn’t want to use <code>--null</code>, we would need to use <code>@(git status --porcelain)</code> to always get an array (but we would also need to remove quotes that <code>git</code> adds to paths that contain spaces).</p>
<h2 id="conclusion">Conclusion</h2>
<p>PowerShell is a fine interactive shell and scripting language. While it does have some warts, it is more powerful than your usual Unix shell, and its strongly-typed, object-oriented code beats <em>stringly-typed</em> <code>sh</code> spaghetti any day.</p>
]]></content:encoded><category>C#/.NET</category><category>.NET</category><category>C#</category><category>PowerShell</category><category>programming</category><category>Windows</category><category>zsh</category></item><item><title>How to improve Python packaging, or why fourteen tools are at least twelve too many</title><dc:creator>Chris Warrick</dc:creator><link>https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/</link><pubDate>Sun, 15 Jan 2023 13:45:00 GMT</pubDate><guid>https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/</guid><description>
There is an area of Python that many developers have problems with. This is an area that has seen many different solutions pop up over the years, with many different opinions, wars, and attempts to solve it. Many have complained about the packaging ecosystem and tools making their lives harder. Many beginners are confused about virtual environments. But does it have to be this way? Are the current solutions to packaging problems any good? And is the organization behind most of the packaging tools and standards part of the problem itself?
Join me on a journey through packaging in Python and elsewhere. We’ll start by describing the classic packaging stack (involving setuptools and friends), the scientific stack (with conda), and some of the modern/alternate tools, such as Pipenv, Poetry, Hatch, or PDM. We’ll also look at some examples of packaging and dependency-related workflows seen elsewhere (Node.js and .NET). We’ll also take a glimpse at a possible future (with a venv-less workflow with PDM), and see if the PyPA agrees with the vision and insights of eight thousand users.
</description><content:encoded><![CDATA[
<p>There is an area of Python that many developers have problems with. This is an area that has seen many different solutions pop up over the years, with many different opinions, wars, and attempts to solve it. Many have complained about the packaging ecosystem and tools making their lives harder. Many beginners are confused about virtual environments. But does it have to be this way? Are the current solutions to packaging problems any good? And is the organization behind most of the packaging tools and standards part of the problem itself?</p>
<p>Join me on a journey through packaging in Python and elsewhere. We’ll start by describing the classic packaging stack (involving setuptools and friends), the scientific stack (with conda), and some of the modern/alternate tools, such as Pipenv, Poetry, Hatch, or PDM. We’ll also look at some examples of packaging and dependency-related workflows seen elsewhere (Node.js and .NET). We’ll also take a glimpse at a possible future (with a venv-less workflow with PDM), and see if the PyPA agrees with the vision and insights of eight thousand users.</p>



<section id="the-plethora-of-tools">
<h1>The plethora of tools</h1>
<p>There are many packaging-related tools in Python. All of them with different authors, lineages, and often different opinions, although most of them are now unified under the Python Packaging Authority (PyPA) umbrella. Let’s take a look at them.</p>
<section id="the-classic-stack">
<h2>The classic stack</h2>
<p>The classic Python packaging stack consists of many semi-related tools. Setuptools, probably the oldest tool of the group, and itself based on <code class="docutils literal">distutils</code>, which is part of the standard library (although it will be removed in Python 3.12), is responsible for installing a single package. It previously used <code class="docutils literal">setup.py</code> files to do its job, which required arbitrary code execution. It then added support for non-executable metadata specification formats: <code class="docutils literal">setup.cfg</code>, and also <code class="docutils literal">pyproject.toml</code> (partially still in beta). However, you aren’t supposed to use <code class="docutils literal">setup.py</code> files directly these days, you’re supposed to be using pip. Pip installs packages, usually from the PyPI, but it can also support other sources (such as git repositories or the local filesystem). But where does pip install things? The default used to be to install globally and system-wide, which meant you could introduce conflicts between packages installed by pip and apt (or whatever the system package manager is). Even with a user-wide install (which pip is likely to attempt these days), you can still end up with conflicts, and you can also have conflicts in which package A requests X version 1.0.0, but package B expects X version 2.0.0—but A and B are not at all related and could live separately with their preferred version of X. Enter <code class="docutils literal">venv</code>, a standard library descendant of <code class="docutils literal">virtualenv</code>, which can create a lightweight virtual environment for packages to live in. This virtual environment gives you the separation from system packages and from different environments, but it is still tied to the system Python in some ways (and if the system Python disappears, the virtual environment stops working).</p>
<p>A few extra tools would be used in a typical packaging workflow. The <code class="docutils literal">wheel</code> package enhances Setuptools with the ability to generate wheels, which are ready-to-install (without running <code class="docutils literal">setup.py</code>). Wheels can either be pure-Python and be installed anywhere, or they can contain pre-compiled extension modules (things written in C) for a given OS and Python (and there’s even a standard that allows building and distributing one wheel for all typical Linux distros). The <code class="docutils literal">wheel</code> package should be an implementation detail, something existing inside Setuptools and/or pip, but users need to be aware of it if they want to make wheels on their system, because virtual environments produced by <code class="docutils literal">venv</code> do not have <code class="docutils literal">wheel</code> installed. Regular users who do not maintain their own packages may sometimes be told that pip is using something legacy because <code class="docutils literal">wheel</code> is not installed, which is not a good user experience. Package authors also need <code class="docutils literal">twine</code>, whose sole task is uploading source distributions or wheels, created with other tools, to PyPI (and there’s not much more to say about that tool).</p>
</section>
<section id="and-a-few-extensions">
<h2>…and a few extensions</h2>
<p>Over the years, there have been a few tools that are based on things from the classic stack. For example, <code class="docutils literal"><span class="pre">pip-tools</span></code> can simplify dependency management. While <code class="docutils literal">pip freeze</code> lets you produce a file with everything installed in your environment, there is no way to specify the dependencies you need, and get a lock file with specific versions and transitive dependencies (without installing and freezing everything), there is no easy way to skip development dependencies (e.g. IPython) when you <code class="docutils literal">pip freeze</code>, and there is no workflow to update all your dependencies with just pip. <code class="docutils literal"><span class="pre">pip-tools</span></code> adds two tools, <code class="docutils literal"><span class="pre">pip-compile</span></code> which takes in <code class="docutils literal">requirements.in</code> files with the packages you care about, and produces a <code class="docutils literal">requrirements.txt</code> with pinned versions of them and all transitive dependencies; and also <code class="docutils literal"><span class="pre">pip-sync</span></code>, which can install <code class="docutils literal">requirements.txt</code> and removes things not listed in it.</p>
<p>Another tool that might come in useful is <code class="docutils literal">virtualenvwrapper</code>, which can help you manage (create and activate) virtual environments in a central location. It has a few bells and whistles (such as custom hooks to do actions on every virtualenv creation), although for basic usage, you could replace it with a single-line shell function.</p>
<p>Yet another tool that works alongside the classic toolset is <code class="docutils literal">pipx</code>, which creates and manages virtual environments for apps written in Python. You tell it to <code class="docutils literal">pipx install Nikola</code>, and it will create a virtual environment somewhere, install Nikola into it, and put a script for launching it in <code class="docutils literal"><span class="pre">~/.local/bin</span></code>. While you could do it all yourself with venv and some symlinks, pipx can take care of this, and you don’t need to remember where the virtual environment is.</p>
</section>
<section id="the-scientific-stack-and-conda">
<h2>The scientific stack and conda</h2>
<p>The scientific Python community have had their own tools for many years. The conda tool can manage environments and packages. It doesn’t use PyPI and wheels, but rather packages from conda channels (which are prebuilt, and expect an Anaconda-distributed Python). Back in the day, when there were no wheels, this was the easiest way to get things installed on Windows; this is not as much of a problem now with binary wheels on PyPI—but the Anaconda stack is still popular in the scientific world. Conda packages can be built with <code class="docutils literal"><span class="pre">conda-build</span></code>, which is separate, but closely related to <code class="docutils literal">conda</code> itself. Conda packages are not compatible with <code class="docutils literal">pip</code> in any way, they do not follow the packaging standards used by other tools. Is this good? No, because it makes integrating the two worlds harder, but also yes, because many problems that apply to scientific packages (and their C/C++ extension modules, and their high-performance numeric libraries, and other things) do not apply to other uses of Python, so having a separate tool lets people focusing the other uses simplify their workflows.</p>
</section>
<section id="the-new-tools">
<h2>The new tools</h2>
<p>A few years ago, new packaging tools appeared. Now, there were lots of “new fancy tools” introduced in the past, with setuptools extending distutils, then distribute forking setuptools, then distribute being merged back…</p>
<p>The earliest “new tool” was Pipenv. Pipenv had really terrible and misleading marketing, and it merged pip and venv, in that Pipenv would create a venv and install packages in it (from <code class="docutils literal">Pipfile</code> or <code class="docutils literal">Pipfile.lock</code>). Pipenv can place the venv in the project folder, or hide it somewhere in the project folder (the latter is the default). However, Pipenv does not handle any packages related to packaging your code, so it’s useful only for developing non-installable applications (Django sites, for example). If you’re a library developer, you need setuptools anyway.</p>
<p>The second new tool was Poetry. It manages environments and dependencies in a similar way to Pipenv, but it can also build <code class="docutils literal">.whl</code> files with your code, and it can upload wheels and source distributions to PyPI. This means it has pretty much all the features the other tools have, except you need just one tool. However, Poetry is opinionated, and its opinions are sometimes incompatible with the rest of the packaging scene. Poetry uses the <code class="docutils literal">pyproject.toml</code> standard, but it does not follow the standard specifying how metadata should be represented in a <code class="docutils literal">pyproject.toml</code> file (PEP 621), instead using a custom <code class="docutils literal">[tool.poetry]</code> table. This is partly because Poetry came out before PEP 621, but the PEP was accepted over 2 years ago—the biggest compatibility problem is Poetry’s node-inspired <code class="docutils literal">~</code> and <code class="docutils literal">^</code> dependency version markers, which are not compatible with PEP 508 (the dependency specification standard). Poetry can package C extension modules, although it uses setuptools’ infrastructure for this (and requires a custom <code class="docutils literal">build.py</code> script).</p>
<p>Another similar tool is Hatch. This tool can also manage environments (it allows multiple environments per project, but it does not allow to put them in the project directory), and it can manage packages (but without lockfile support). Hatch can also be used to package a project (with PEP 621-compliant <code class="docutils literal">pyproject.toml</code> files) and upload it to PyPI. It does not support C extension modules.</p>
<p>A tool that tries to be a simpler re-imagining of Setuptools is Flit. It can build and install a package using a <code class="docutils literal">pyproject.toml</code> file. It also supports uploads to PyPI. It lacks support for C extension modules, and it expects you to manage environments on your own.</p>
<p>There’s one more interesting (albeit not popular or well-known) tool. This tool is PDM. It can manage venvs (but it defaults to the saner <code class="docutils literal">.venv</code> location), manage dependencies, and it uses a standards-compliant <code class="docutils literal">pyproject.toml</code>. There’s also a curious little feature called PEP 582 support, which we’ll talk about later.</p>
</section>
</section>
<section id="tooling-proliferation-and-the-python-package-authority">
<h1>Tooling proliferation and the Python Package Authority</h1>
<p>The previous sections mentioned 14 (fourteen!) distinct tools. As we’ll discover soon, that’s at least 12 too many. Let’s try to compare them.</p>
<p>First, let’s define nine things that we would expect packaging tools to do:</p>
<ol class="arabic simple">
<li><p>Manage environments</p></li>
<li><p>Install packages</p></li>
<li><p>Package/develop apps</p></li>
<li><p>Package libraries</p></li>
<li><p>Package C extension modules</p></li>
<li><p>Install in editable mode</p></li>
<li><p>Lock dependencies</p></li>
<li><p>Support pyproject.toml files</p></li>
<li><p>Upload to PyPI</p></li>
</ol>
<div style="font-size: 90%"><table class="table table-hover">
<thead>
<tr><th class="head"><p>Tool</p></th>
<th class="head"><p>Maintainer</p></th>
<th class="head"><p>Use-case</p></th>
<th class="head"><p># of supported features</p></th>
<th class="head"><p># of partially supported features</p></th>
<th class="head"><p># of unsupported features</p></th>
</tr>
</thead>
<tbody>
<tr><td><p>setuptools</p></td>
<td><p>PyPA</p></td>
<td><p>Making things installable</p></td>
<td><p>4</p></td>
<td><p>2 (pyproject.toml partially in beta, installing—only setuptools-based sdists)</p></td>
<td><p>3</p></td>
</tr>
<tr><td><p>pip</p></td>
<td><p>PyPA</p></td>
<td><p>Installing packages</p></td>
<td><p>2</p></td>
<td><p>1 (Locking dependencies only manually)</p></td>
<td><p>6</p></td>
</tr>
<tr><td><p>venv</p></td>
<td><p>PyPA</p></td>
<td><p>Creating virtual environments</p></td>
<td><p>1 (Creating environments)</p></td>
<td><p>0</p></td>
<td><p>8</p></td>
</tr>
<tr><td><p>wheel</p></td>
<td><p>PyPA</p></td>
<td><p>Building wheels in setuptools</p></td>
<td><p>0</p></td>
<td><p>1 (Building wheels in setuptools)</p></td>
<td><p>8</p></td>
</tr>
<tr><td><p>Twine</p></td>
<td><p>PyPA</p></td>
<td><p>Uploading to PyPI</p></td>
<td><p>1 (Uploading to PyPI)</p></td>
<td><p>0</p></td>
<td><p>8</p></td>
</tr>
<tr><td><p>pip-tools</p></td>
<td><p>Jazzband</p></td>
<td><p>Managing requirements files</p></td>
<td><p>2 (Locking dependencies, installing packages)</p></td>
<td><p>0</p></td>
<td><p>7</p></td>
</tr>
<tr><td><p>virtualenvwrapper</p></td>
<td><p>Doug Hellmann</p></td>
<td><p>Managing virtual environments</p></td>
<td><p>1 (Managing environments)</p></td>
<td><p>0</p></td>
<td><p>8</p></td>
</tr>
<tr><td><p>pipx</p></td>
<td><p>PyPA</p></td>
<td><p>Installing Python command-line tools</p></td>
<td><p>2 (Installing packages, editable installs)</p></td>
<td><p>1 (Managing environments)</p></td>
<td><p>6</p></td>
</tr>
<tr><td><p>conda</p></td>
<td><p>Anaconda, Inc.</p></td>
<td><p>Managing environments and dependencies</p></td>
<td><p>3 (Managing environments, installing things)</p></td>
<td><p>4 (Manual locking, packaging requires conda-build)</p></td>
<td><p>2 (pyproject.toml and PyPI)</p></td>
</tr>
<tr><td><p>Pipenv</p></td>
<td><p>PyPA</p></td>
<td><p>Managing dependencies for apps</p></td>
<td><p>3 (Managing environments, installing and locking)</p></td>
<td><p>1 (Developing apps)</p></td>
<td><p>5</p></td>
</tr>
<tr><td><p>Poetry</p></td>
<td><p>Sébastien Eustace et al.</p></td>
<td><p>Packaging and managing dependencies</p></td>
<td><p>7</p></td>
<td><p>2 (pyproject.toml, C extensions)</p></td>
<td><p>0</p></td>
</tr>
<tr><td><p>Flit</p></td>
<td><p>PyPA</p></td>
<td><p>Packaging pure-Python projects</p></td>
<td><p>5</p></td>
<td><p>1 (Installing only flit packages)</p></td>
<td><p>3</p></td>
</tr>
<tr><td><p>Hatch</p></td>
<td><p>PyPA</p></td>
<td><p>Packaging and managing dependencies</p></td>
<td><p>7</p></td>
<td><p>0</p></td>
<td><p>2 (C extensions, locking dependencies)</p></td>
</tr>
<tr><td><p>PDM</p></td>
<td><p>Frost Ming</p></td>
<td><p>Packaging and managing dependencies</p></td>
<td><p>8</p></td>
<td><p>0</p></td>
<td><p>1 (C extensions)</p></td>
</tr>
</tbody>
</table>
</div>
<details style="margin-bottom: 1rem">
<summary style="background: rgba(0, 170, 221, 10%); padding: .25rem; border-radius: .25rem">Expand table with more details about support for each feature</summary>
<div style="font-size: 90%; margin-top: .5rem"><table class="table table-hover">
<thead>
<tr><th class="head"><p>Tool</p></th>
<th class="head"><p>F1 (Envs)</p></th>
<th class="head"><p>F2 (Install)</p></th>
<th class="head"><p>F3 (Apps)</p></th>
<th class="head"><p>F4 (Libraries)</p></th>
<th class="head"><p>F5 (Extensions)</p></th>
<th class="head"><p>F6 (Editable)</p></th>
<th class="head"><p>F7 (Lock)</p></th>
<th class="head"><p>F8 (pyproject.toml)</p></th>
<th class="head"><p>F9 (Upload)</p></th>
</tr>
</thead>
<tbody>
<tr><td><p>setuptools</p></td>
<td><p>No</p></td>
<td><p>Only if authoring the package, direct use not recommended</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>No</p></td>
<td><p>Beta</p></td>
<td><p>No (can build sdist)</p></td>
</tr>
<tr><td><p>pip</p></td>
<td><p>No</p></td>
<td><p>Yes</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>Yes</p></td>
<td><p>Manually</p></td>
<td><p>N/A</p></td>
<td><p>No</p></td>
</tr>
<tr><td><p>venv</p></td>
<td><p>Only creating environments</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
</tr>
<tr><td><p>wheel</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No (can build wheels)</p></td>
</tr>
<tr><td><p>Twine</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>Yes</p></td>
</tr>
<tr><td><p>pip-tools</p></td>
<td><p>No</p></td>
<td><p>Yes</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>Yes</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
</tr>
<tr><td><p>virtualenvwrapper</p></td>
<td><p>Yes</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
</tr>
<tr><td><p>pipx</p></td>
<td><p>Sort of</p></td>
<td><p>Yes</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>Yes</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
</tr>
<tr><td><p>conda</p></td>
<td><p>Yes</p></td>
<td><p>Yes (from conda channels)</p></td>
<td><p>Develop (conda-build is a separate tool)</p></td>
<td><p>With conda-build</p></td>
<td><p>With conda-build</p></td>
<td><p>Yes</p></td>
<td><p>Manually</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
</tr>
<tr><td><p>Pipenv</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>Only develop</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
<td><p>Yes</p></td>
<td><p>No</p></td>
<td><p>No</p></td>
</tr>
<tr><td><p>Poetry</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>Sort of (custom build.py script)</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>Yes, but using custom fields</p></td>
<td><p>Yes</p></td>
</tr>
<tr><td><p>Flit</p></td>
<td><p>No</p></td>
<td><p>Only if authoring the package</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>No</p></td>
<td><p>Yes</p></td>
<td><p>No</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
</tr>
<tr><td><p>Hatch</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>No</p></td>
<td><p>Yes</p></td>
<td><p>No</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
</tr>
<tr><td><p>PDM</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>No</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
<td><p>Yes</p></td>
</tr>
</tbody>
</table>
</div>
</details><p>You should pay close attention to the Maintainer column in the table. The vast majority of them are maintained by PyPA, the Python Packaging Authority. Even more curiously, the two tools that have the most “Yes” values (Poetry and PDM) are not maintained by the PyPA, but instead other people completely independent of them and not participating in the working group. So, is the working group successful, if it cannot produce one fully-featured tool? Is the group successful if it has multiple projects with overlapping responsibilities? Should the group focus their efforts on standards like <a class="reference external" href="https://peps.python.org/pep-0517/">PEP 517</a>, which is a common API for packaging tools, and which also encourages the creation of even more incompatible and competing tools?</p>
<p>Most importantly: which tool should a beginner use? The PyPA has a few guides and tutorials, one is <a class="reference external" href="https://packaging.python.org/en/latest/tutorials/installing-packages/">using pip + venv</a>, another is <a class="reference external" href="https://packaging.python.org/en/latest/tutorials/managing-dependencies/">using pipenv</a> (why would you still do that?), and <a class="reference external" href="https://packaging.python.org/en/latest/tutorials/packaging-projects/">another tutorial</a> that lets you pick between Hatchling (hatch’s build backend), setuptools, Flit, and PDM, without explaining the differences between them—and without using any environment tools, and without using Hatch’s/PDM’s build and PyPI upload features (instead opting to use <code class="docutils literal">python <span class="pre">-m</span> build</code> and <code class="docutils literal">twine</code>). The concept of virtual environments can be very confusing for beginners, and managing virtual environments is difficult if everyone has incompatible opinions about it.</p>
<p>It is also notable that <a class="reference external" href="https://peps.python.org/pep-0020/">PEP 20</a>, the Zen of Python, states this:</p>
<blockquote>
<p><em>There should be one-- and preferably only one --obvious way to do it.</em></p>
</blockquote>
<p>Python packaging definitely does not follow it <a class="brackets" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-1" id="footnote-reference-1" role="doc-noteref"><span class="fn-bracket">[</span>1<span class="fn-bracket">]</span></a>. There are 14 ways, and none of them is obvious or the only good one. All in all, this is an unsalvageable mess. Why can’t Python pick one tool? What does the competition do? We’ll look at this in a minute. But first, let’s talk about the elephant in the room: Python virtual environments.</p>
</section>
<section id="does-python-really-need-virtual-environments">
<h1>Does Python really need virtual environments?</h1>
<p>Python relies on virtual environments for separation between projects. Virtual environments (aka virtualenvs or venvs) are folders with symlinks to a system-installed Python, and their own set of site-packages. There are a few problems with them:</p>
<section id="how-to-use-python-from-a-virtual-environment">
<h2>How to use Python from a virtual environment?</h2>
<p>There are two ways to do this. The first one is to activate it, by running the activate shell script installed in the environment’s bin directory. Another is to run the python executable (or any other script in the bin directory) directly from the venv. <a class="brackets" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-2" id="footnote-reference-2" role="doc-noteref"><span class="fn-bracket">[</span>2<span class="fn-bracket">]</span></a></p>
<p>Activating venvs directly is more convenient for developers, but it also has some problems. Sometimes, activation fails to work, due to the shell caching the location of things in <code class="docutils literal">$PATH</code>. Also, beginners are taught to <code class="docutils literal">activate</code> and run <code class="docutils literal">python</code>, which means they might be confused and try to use activate in scripts or cronjobs (but in those environments, you should not activate venvs, and instead use the Python executable directly). Virtual environment activation is more state you need to be aware of, and if you forget about it, or if it breaks, you might end up messing up your user-wide (or worse, system-wide) Python packages.</p>
</section>
<section id="how-are-system-pythons-and-virtual-environments-related">
<h2>How are (system) Pythons and virtual environments related?</h2>
<p>The virtual environment depends very tightly on the (system/global/pyenv-installed) Python used to create it. This is good for disk-space reasons (clean virtual environments don’t take up very much space), but this also makes the environment more fragile. If the Python used to create the environment is removed, the virtual environment stops working. If you fully manage your own Python, then it’s probably not going to happen, but if you depend on a system Python, upgrading packages on your OS might end up replacing Python 3.10 with Python 3.11. Some distributions (e.g. Ubuntu) would only make a jump like this on a new distribution release (so you can plan ahead), some of them (e.g. Arch) are rolling-release and a regular system upgrade may include a new Python, whereas some (e.g. Homebrew) make it even worse by using paths that include the patch Python version (3.x.y), which cause virtual environments to break much more often.</p>
</section>
<section id="how-to-manage-virtual-environments">
<h2>How to manage virtual environments?</h2>
<p>The original virtualenv tool, and its simplified standard library rewrite venv, allow you to put a virtual environment anywhere in the file system, as long as you have write privileges there. This has led to people and tools inventing their own standards. Virtualenvwrapper stores environments in a central location, and does not care about their contents. Pipenv and poetry allow you to choose (either a central location or the .venv directory in the project), and environments are tied to a project (they will use the project-specific environment if you’re in the project directory). Hatch stores environments in a central location, and it allows you to have multiple environments per project (but there is no option to share environments between projects).</p>
<p>Brett Cannon has recently done <a class="reference external" href="https://snarky.ca/classifying-python-virtual-environment-workflows/">a survey</a>, and it has shown the community is split on their workflows: some people use a central location, some put them in the project directory, some people have multiple environments with different Python versions, some people reuse virtualenvs between projects… Everyone has different needs, and different opinions. For example, I use a central directory (~/virtualenvs) and reuse environments when working on Nikola (sharing the same environment between development and 4 Nikola sites). But on the other hand, when deploying web apps, the venv lives in the project folder, because this venv needs to be used by processes running as different users (me, root, or the service account for the web server, which might have interactive login disabled, or whose home directory may be set to something ephemeral).</p>
<p>So: <strong>does Python need virtual environments?</strong> Perhaps looking how other languages handle this problem can help us figure this out for Python?</p>
</section>
</section>
<section id="how-everyone-else-is-doing-it">
<h1>How everyone else is doing it</h1>
<p>We’ll look at two ecosystems. We’ll start with <a class="reference internal" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#javascript-node-js-with-npm">JavaScript/Node.js (with npm)</a>, and then we’ll look at the <a class="reference internal" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#c-net-with-dotnet-cli-msbuild">C#/.NET (with dotnet CLI/MSBuild)</a> ecosystem for comparison. We’ll demonstrate a sample flow of making a project, installing dependencies in it, and running things. If you’re familiar with those ecosystems and want to skip the examples, continue with <a class="reference internal" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#how-is-node-better-than-python">How is Node better than Python?</a> and <a class="reference internal" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#are-those-ecosystems-tools-perfect">Are those ecosystems’ tools perfect?</a>. Otherwise, read on.</p>
<section id="javascript-node-js-with-npm">
<h2>JavaScript/Node.js (with npm)</h2>
<p>There are two tools for dealing with packages in the Node world, namely npm and Yarn. The npm CLI tool is shipped with Node, so we’ll focus on it.</p>
<p>Let’s create a project:</p>
<div class="code"><pre class="code text"><a id="rest_code_21bf981bce0e4148bc3f2129aa4786ec-1" name="rest_code_21bf981bce0e4148bc3f2129aa4786ec-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_21bf981bce0e4148bc3f2129aa4786ec-1"></a>$ mkdir mynpmproject
<a id="rest_code_21bf981bce0e4148bc3f2129aa4786ec-2" name="rest_code_21bf981bce0e4148bc3f2129aa4786ec-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_21bf981bce0e4148bc3f2129aa4786ec-2"></a>$ cd mynpmproject
<a id="rest_code_21bf981bce0e4148bc3f2129aa4786ec-3" name="rest_code_21bf981bce0e4148bc3f2129aa4786ec-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_21bf981bce0e4148bc3f2129aa4786ec-3"></a>$ npm init
<a id="rest_code_21bf981bce0e4148bc3f2129aa4786ec-4" name="rest_code_21bf981bce0e4148bc3f2129aa4786ec-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_21bf981bce0e4148bc3f2129aa4786ec-4"></a>…answer a few questions…
<a id="rest_code_21bf981bce0e4148bc3f2129aa4786ec-5" name="rest_code_21bf981bce0e4148bc3f2129aa4786ec-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_21bf981bce0e4148bc3f2129aa4786ec-5"></a>$ ls
<a id="rest_code_21bf981bce0e4148bc3f2129aa4786ec-6" name="rest_code_21bf981bce0e4148bc3f2129aa4786ec-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_21bf981bce0e4148bc3f2129aa4786ec-6"></a>package.json
</pre></div>
<p>We’ve got a package.json file, which has some metadata about our project (name, version, description, license). Let’s install a dependency:</p>
<div class="code"><pre class="code text"><a id="rest_code_46c57d5e926a4f7f9dd6ae4afd442928-1" name="rest_code_46c57d5e926a4f7f9dd6ae4afd442928-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_46c57d5e926a4f7f9dd6ae4afd442928-1"></a>$ npm install --save is-even
<a id="rest_code_46c57d5e926a4f7f9dd6ae4afd442928-2" name="rest_code_46c57d5e926a4f7f9dd6ae4afd442928-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_46c57d5e926a4f7f9dd6ae4afd442928-2"></a>
<a id="rest_code_46c57d5e926a4f7f9dd6ae4afd442928-3" name="rest_code_46c57d5e926a4f7f9dd6ae4afd442928-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_46c57d5e926a4f7f9dd6ae4afd442928-3"></a>added 5 packages, and audited 6 packages in 2s
<a id="rest_code_46c57d5e926a4f7f9dd6ae4afd442928-4" name="rest_code_46c57d5e926a4f7f9dd6ae4afd442928-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_46c57d5e926a4f7f9dd6ae4afd442928-4"></a>
<a id="rest_code_46c57d5e926a4f7f9dd6ae4afd442928-5" name="rest_code_46c57d5e926a4f7f9dd6ae4afd442928-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_46c57d5e926a4f7f9dd6ae4afd442928-5"></a>found 0 vulnerabilities
</pre></div>
<p>The mere existence of an <code class="docutils literal"><span class="pre">is-even</span></code> package is questionable; the fact that it includes four dependencies is yet another, and the fact that it depends on <code class="docutils literal"><span class="pre">is-odd</span></code> is even worse. But this post isn’t about <code class="docutils literal"><span class="pre">is-even</span></code> or the Node ecosystem’s tendency to use tiny packages for everything (but I wrote one about this topic <a class="reference external" href="https://chriswarrick.com/blog/2019/02/15/modern-web-development-where-you-need-500-packages-to-build-bootstrap/">before</a>). Let’s look at what we have in the filesystem:</p>
<div class="code"><pre class="code text"><a id="rest_code_b7b59a6104744d158a65decb4d8b848f-1" name="rest_code_b7b59a6104744d158a65decb4d8b848f-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_b7b59a6104744d158a65decb4d8b848f-1"></a>$ ls
<a id="rest_code_b7b59a6104744d158a65decb4d8b848f-2" name="rest_code_b7b59a6104744d158a65decb4d8b848f-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_b7b59a6104744d158a65decb4d8b848f-2"></a>node_modules/  package.json  package-lock.json
<a id="rest_code_b7b59a6104744d158a65decb4d8b848f-3" name="rest_code_b7b59a6104744d158a65decb4d8b848f-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_b7b59a6104744d158a65decb4d8b848f-3"></a>$ ls node_modules
<a id="rest_code_b7b59a6104744d158a65decb4d8b848f-4" name="rest_code_b7b59a6104744d158a65decb4d8b848f-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_b7b59a6104744d158a65decb4d8b848f-4"></a>is-buffer/  is-even/  is-number/  is-odd/  kind-of/
</pre></div>
<p>Let’s also take a peek at the <code class="docutils literal">package.json</code> file:</p>
<div class="code"><pre class="code json"><a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-1" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-1"></a><span class="p">{</span>
<a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-2" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-2"></a><span class="w">  </span><span class="nt">&quot;name&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;mynpmproject&quot;</span><span class="p">,</span>
<a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-3" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-3"></a><span class="w">  </span><span class="nt">&quot;version&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;1.0.0&quot;</span><span class="p">,</span>
<a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-4" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-4"></a><span class="w">  </span><span class="nt">&quot;description&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;&quot;</span><span class="p">,</span>
<a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-5" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-5"></a><span class="w">  </span><span class="nt">&quot;main&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;index.js&quot;</span><span class="p">,</span>
<a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-6" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-6"></a><span class="w">  </span><span class="nt">&quot;scripts&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-7" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-7" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-7"></a><span class="w">    </span><span class="nt">&quot;test&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;echo \&quot;Error: no test specified\&quot; &amp;&amp; exit 1&quot;</span>
<a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-8" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-8" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-8"></a><span class="w">  </span><span class="p">},</span>
<a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-9" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-9" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-9"></a><span class="w">  </span><span class="nt">&quot;author&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;&quot;</span><span class="p">,</span>
<a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-10" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-10" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-10"></a><span class="w">  </span><span class="nt">&quot;license&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;ISC&quot;</span><span class="p">,</span>
<a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-11" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-11" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-11"></a><span class="w">  </span><span class="nt">&quot;dependencies&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-12" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-12" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-12"></a><span class="w">    </span><span class="nt">&quot;is-even&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;^1.0.0&quot;</span>
<a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-13" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-13" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-13"></a><span class="w">  </span><span class="p">}</span>
<a id="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-14" name="rest_code_2d75fbb0bc1d4353b74ed823295e39d5-14" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_2d75fbb0bc1d4353b74ed823295e39d5-14"></a><span class="p">}</span>
</pre></div>
<p>Our <code class="docutils literal">package.json</code> file now lists the dependency, and we’ve also got a lock file (<code class="docutils literal"><span class="pre">package-lock.json</span></code>), which records all the dependency versions used for this install. If this file is kept in the repository, any future attempts to <code class="docutils literal">npm install</code> will use the dependency versions listed in this file, ensuring everything will work the same as it did originally (unless one of those packages were to get removed from the registry).</p>
<p>Let’s try writing a trivial program using the module and try running it:</p>
<div class="code"><pre class="code text"><a id="rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-1" name="rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-1"></a>$ cat index.js
<a id="rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-2" name="rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-2"></a>var isEven = require(&#39;is-even&#39;);
<a id="rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-3" name="rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-3"></a>console.log(isEven(0));
<a id="rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-4" name="rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-4"></a>
<a id="rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-5" name="rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-5"></a>$ node index.js
<a id="rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-6" name="rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_e7fb3d8f089d440f9b0f5b9eff4e3e16-6"></a>true
</pre></div>
<p>Let’s try removing <code class="docutils literal"><span class="pre">is-odd</span></code> to demonstrate how badly designed this package is:</p>
<div class="code"><pre class="code text"><a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-1" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-1"></a>$ rm -rf node_modules/is-odd
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-2" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-2"></a>$ node index.js
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-3" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-3"></a>node:internal/modules/cjs/loader:998
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-4" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-4"></a>  throw err;
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-5" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-5"></a>  ^
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-6" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-6"></a>
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-7" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-7" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-7"></a>Error: Cannot find module &#39;is-odd&#39;
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-8" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-8" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-8"></a>Require stack:
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-9" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-9" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-9"></a>- /tmp/mynpmproject/node_modules/is-even/index.js
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-10" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-10" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-10"></a>- /tmp/mynpmproject/index.js
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-11" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-11" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-11"></a>    at Module._resolveFilename (node:internal/modules/cjs/loader:995:15)
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-12" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-12" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-12"></a>    at Module._load (node:internal/modules/cjs/loader:841:27)
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-13" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-13" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-13"></a>    at Module.require (node:internal/modules/cjs/loader:1061:19)
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-14" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-14" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-14"></a>    at require (node:internal/modules/cjs/helpers:103:18)
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-15" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-15" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-15"></a>    at Object.&lt;anonymous&gt; (/tmp/mynpmproject/node_modules/is-even/index.js:10:13)
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-16" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-16" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-16"></a>    at Module._compile (node:internal/modules/cjs/loader:1159:14)
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-17" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-17" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-17"></a>    at Module._extensions..js (node:internal/modules/cjs/loader:1213:10)
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-18" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-18" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-18"></a>    at Module.load (node:internal/modules/cjs/loader:1037:32)
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-19" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-19" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-19"></a>    at Module._load (node:internal/modules/cjs/loader:878:12)
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-20" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-20" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-20"></a>    at Module.require (node:internal/modules/cjs/loader:1061:19) {
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-21" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-21" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-21"></a>  code: &#39;MODULE_NOT_FOUND&#39;,
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-22" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-22" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-22"></a>  requireStack: [
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-23" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-23" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-23"></a>    &#39;/tmp/mynpmproject/node_modules/is-even/index.js&#39;,
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-24" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-24" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-24"></a>    &#39;/tmp/mynpmproject/index.js&#39;
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-25" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-25" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-25"></a>  ]
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-26" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-26" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-26"></a>}
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-27" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-27" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-27"></a>
<a id="rest_code_1d4b90082f5249fe9d590a962f9584c8-28" name="rest_code_1d4b90082f5249fe9d590a962f9584c8-28" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_1d4b90082f5249fe9d590a962f9584c8-28"></a>Node.js v18.12.1
</pre></div>
<section id="how-is-node-better-than-python">
<h3>How is Node better than Python?</h3>
<p>Badly designed packages aside, we can see an important difference from Python in that there is <strong>no virtual environment</strong>, and all the packages live in the project directory. If we fix the <code class="docutils literal">node_modules</code> directory by running <code class="docutils literal">npm install</code>, we can see that I can run the script from somewhere else on the file system:</p>
<div class="code"><pre class="code text"><a id="rest_code_bc27cf0914284bc4be97070d5b0880e8-1" name="rest_code_bc27cf0914284bc4be97070d5b0880e8-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_bc27cf0914284bc4be97070d5b0880e8-1"></a>$ pwd
<a id="rest_code_bc27cf0914284bc4be97070d5b0880e8-2" name="rest_code_bc27cf0914284bc4be97070d5b0880e8-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_bc27cf0914284bc4be97070d5b0880e8-2"></a>/tmp/mynpmproject
<a id="rest_code_bc27cf0914284bc4be97070d5b0880e8-3" name="rest_code_bc27cf0914284bc4be97070d5b0880e8-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_bc27cf0914284bc4be97070d5b0880e8-3"></a>$ npm install
<a id="rest_code_bc27cf0914284bc4be97070d5b0880e8-4" name="rest_code_bc27cf0914284bc4be97070d5b0880e8-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_bc27cf0914284bc4be97070d5b0880e8-4"></a>
<a id="rest_code_bc27cf0914284bc4be97070d5b0880e8-5" name="rest_code_bc27cf0914284bc4be97070d5b0880e8-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_bc27cf0914284bc4be97070d5b0880e8-5"></a>added 1 package, and audited 6 packages in 436ms
<a id="rest_code_bc27cf0914284bc4be97070d5b0880e8-6" name="rest_code_bc27cf0914284bc4be97070d5b0880e8-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_bc27cf0914284bc4be97070d5b0880e8-6"></a>
<a id="rest_code_bc27cf0914284bc4be97070d5b0880e8-7" name="rest_code_bc27cf0914284bc4be97070d5b0880e8-7" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_bc27cf0914284bc4be97070d5b0880e8-7"></a>found 0 vulnerabilities
<a id="rest_code_bc27cf0914284bc4be97070d5b0880e8-8" name="rest_code_bc27cf0914284bc4be97070d5b0880e8-8" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_bc27cf0914284bc4be97070d5b0880e8-8"></a>$ node /tmp/mynpmproject/index.js
<a id="rest_code_bc27cf0914284bc4be97070d5b0880e8-9" name="rest_code_bc27cf0914284bc4be97070d5b0880e8-9" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_bc27cf0914284bc4be97070d5b0880e8-9"></a>true
<a id="rest_code_bc27cf0914284bc4be97070d5b0880e8-10" name="rest_code_bc27cf0914284bc4be97070d5b0880e8-10" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_bc27cf0914284bc4be97070d5b0880e8-10"></a>$ cd ~
<a id="rest_code_bc27cf0914284bc4be97070d5b0880e8-11" name="rest_code_bc27cf0914284bc4be97070d5b0880e8-11" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_bc27cf0914284bc4be97070d5b0880e8-11"></a>$ node /tmp/mynpmproject/index.js
<a id="rest_code_bc27cf0914284bc4be97070d5b0880e8-12" name="rest_code_bc27cf0914284bc4be97070d5b0880e8-12" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_bc27cf0914284bc4be97070d5b0880e8-12"></a>true
</pre></div>
<p><strong>If you try to do that with a Python tool…</strong></p>
<ul class="simple">
<li><p>If you’re using a manually managed venv, you need to remember to activate it, or to use the appropriate Python.</p></li>
<li><p>If you’re using something fancier, it might be tied to the current working directory, and it may expect you to change into that directory, or to pass an argument pointing at that directory.</p></li>
</ul>
<p>I can also run my code as <code class="docutils literal">root</code>, and as an unprivileged <code class="docutils literal">nginx</code> user, without any special preparation (like telling pipenv/poetry to put their venv in the project directory, or running them as the other users):</p>
<div class="code"><pre class="code text"><a id="rest_code_eca6b7c0c3f0414097f504b2b256ead7-1" name="rest_code_eca6b7c0c3f0414097f504b2b256ead7-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_eca6b7c0c3f0414097f504b2b256ead7-1"></a>$ su -
<a id="rest_code_eca6b7c0c3f0414097f504b2b256ead7-2" name="rest_code_eca6b7c0c3f0414097f504b2b256ead7-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_eca6b7c0c3f0414097f504b2b256ead7-2"></a># node /tmp/mynpmproject/index.js
<a id="rest_code_eca6b7c0c3f0414097f504b2b256ead7-3" name="rest_code_eca6b7c0c3f0414097f504b2b256ead7-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_eca6b7c0c3f0414097f504b2b256ead7-3"></a>true
<a id="rest_code_eca6b7c0c3f0414097f504b2b256ead7-4" name="rest_code_eca6b7c0c3f0414097f504b2b256ead7-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_eca6b7c0c3f0414097f504b2b256ead7-4"></a># sudo -u nginx node /tmp/mynpmproject/index.js
<a id="rest_code_eca6b7c0c3f0414097f504b2b256ead7-5" name="rest_code_eca6b7c0c3f0414097f504b2b256ead7-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_eca6b7c0c3f0414097f504b2b256ead7-5"></a>true
</pre></div>
<p><strong>If you try to do that with a Python tool…</strong></p>
<ul class="simple">
<li><p>If you’re using a manually managed venv, you can use its Python as another user (assuming it has the right permissions).</p></li>
<li><p>If your tool puts the venv in the project directory, this will work too.</p></li>
<li><p>If your tool puts the venv in some weird place in your home folder, the other users will get their own venvs. The <code class="docutils literal">uwsgi</code> user on Fedora uses <code class="docutils literal">/run/uwsgi</code> as its home directory, and <code class="docutils literal">/run</code> is ephemeral (tmpfs), so a reboot forces you to reinstall things.</p></li>
</ul>
<p>We can even try to change the name of our project:</p>
<div class="code"><pre class="code text"><a id="rest_code_336d5b5f844a4ec6a1fac2fd04133907-1" name="rest_code_336d5b5f844a4ec6a1fac2fd04133907-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_336d5b5f844a4ec6a1fac2fd04133907-1"></a>$ cd /tmp
<a id="rest_code_336d5b5f844a4ec6a1fac2fd04133907-2" name="rest_code_336d5b5f844a4ec6a1fac2fd04133907-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_336d5b5f844a4ec6a1fac2fd04133907-2"></a>$ mv mynpmproject mynodeproject
<a id="rest_code_336d5b5f844a4ec6a1fac2fd04133907-3" name="rest_code_336d5b5f844a4ec6a1fac2fd04133907-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_336d5b5f844a4ec6a1fac2fd04133907-3"></a>$ node /tmp/mynodeproject/index.js
<a id="rest_code_336d5b5f844a4ec6a1fac2fd04133907-4" name="rest_code_336d5b5f844a4ec6a1fac2fd04133907-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_336d5b5f844a4ec6a1fac2fd04133907-4"></a>true
</pre></div>
<p><strong>If you try to do that with a Python tool…</strong></p>
<ul class="simple">
<li><p>If you’re using a manually managed venv, and it lives in a central directory, all is well.</p></li>
<li><p>If you or your tool places the venv in the project directory, the venv is now broken, and you need to recreate it (hope you have a recent <code class="docutils literal">requirements.txt</code>!)</p></li>
<li><p>If your tool puts the venv in some weird place in your home folder, it may decide that this is a different project, which means it will recreate it, and you’ll have an unused virtual environment somewhere on your filesystem.</p></li>
</ul>
</section>
<section id="other-packaging-topics">
<h3>Other packaging topics</h3>
<p>Some packages may expose executable scripts (with the <code class="docutils literal">bin</code> property). Those can be run in three ways:</p>
<ol class="arabic simple">
<li><p>Installed globally using <code class="docutils literal">npm install <span class="pre">-g</span></code>, which would put the script in a global location that’s likely in <code class="docutils literal">$PATH</code> (e.g. <code class="docutils literal">/usr/local/bin</code>).</p></li>
<li><p>Installed locally using <code class="docutils literal">npm install</code>, and executed with the <code class="docutils literal">npx</code> tool or manually by running the script in <code class="docutils literal"><span class="pre">node_packages/.bin</span></code>.</p></li>
<li><p>Not installed at all, but executed using the <code class="docutils literal">npx</code> tool, which will install it into a cache and run it.</p></li>
</ol>
<p>Also, if we wanted to publish our thing, we can just run <code class="docutils literal">npm publish</code> (after logging in with <code class="docutils literal">npm login</code>).</p>
</section>
</section>
<section id="c-net-with-dotnet-cli-msbuild">
<h2>C#/.NET (with dotnet CLI/MSBuild)</h2>
<p>In modern .NET, the One True Tool is the dotnet CLI, which uses MSBuild for most of the heavy lifting. (In the classic .NET Framework, the duties were split between MSBuild and NuGet.exe, but let’s focus on the modern workflow.)</p>
<p>Let’s create a project:</p>
<div class="code"><pre class="code text"><a id="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-1" name="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-1"></a>$ mkdir mydotnetproject
<a id="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-2" name="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-2"></a>$ cd mydotnetproject
<a id="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-3" name="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-3"></a>$ dotnet new console
<a id="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-4" name="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-4"></a>The template &quot;Console App&quot; was created successfully.
<a id="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-5" name="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-5"></a>
<a id="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-6" name="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-6"></a>Processing post-creation actions...
<a id="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-7" name="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-7" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-7"></a>Running &#39;dotnet restore&#39; on /tmp/mydotnetproject/mydotnetproject.csproj...
<a id="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-8" name="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-8" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-8"></a>  Determining projects to restore...
<a id="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-9" name="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-9" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-9"></a>  Restored /tmp/mydotnetproject/mydotnetproject.csproj (in 92 ms).
<a id="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-10" name="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-10" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-10"></a>Restore succeeded.
<a id="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-11" name="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-11" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-11"></a>$ ls
<a id="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-12" name="rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-12" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_5b2a5a2f4beb43b1a1391c52a8baf0e3-12"></a>mydotnetproject.csproj  obj/  Program.cs
</pre></div>
<p>We get three things: a <code class="docutils literal">mydotnetproject.csproj</code> file, which defines a few properties of our project; <code class="docutils literal">Program.cs</code>, which is a hello world program, and <code class="docutils literal">obj/</code>, which contains a few files you don’t need to care about.</p>
<p>Let’s try adding a dependency. For a pointless example, but slightly more reasonable than the JS one, we’ll use <code class="docutils literal">AutoFixture</code>, which brings in a dependency on <code class="docutils literal">Fare</code>. If we run <code class="docutils literal">dotnet add package AutoFixture</code>, we get some console output, and our <code class="docutils literal">mydotnetproject.csproj</code> now looks like this:</p>
<div class="code"><pre class="code xml"><a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-1" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-1"></a><span class="nt">&lt;Project</span><span class="w"> </span><span class="na">Sdk=</span><span class="s">&quot;Microsoft.NET.Sdk&quot;</span><span class="nt">&gt;</span>
<a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-2" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-2"></a>
<a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-3" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-3"></a><span class="w">  </span><span class="nt">&lt;PropertyGroup&gt;</span>
<a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-4" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-4"></a><span class="w">    </span><span class="nt">&lt;OutputType&gt;</span>Exe<span class="nt">&lt;/OutputType&gt;</span>
<a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-5" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-5"></a><span class="w">    </span><span class="nt">&lt;TargetFramework&gt;</span>net6.0<span class="nt">&lt;/TargetFramework&gt;</span>
<a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-6" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-6"></a><span class="w">    </span><span class="nt">&lt;ImplicitUsings&gt;</span>enable<span class="nt">&lt;/ImplicitUsings&gt;</span>
<a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-7" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-7" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-7"></a><span class="w">    </span><span class="nt">&lt;Nullable&gt;</span>enable<span class="nt">&lt;/Nullable&gt;</span>
<a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-8" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-8" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-8"></a><span class="w">  </span><span class="nt">&lt;/PropertyGroup&gt;</span>
<a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-9" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-9" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-9"></a>
<a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-10" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-10" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-10"></a><span class="w">  </span><span class="nt">&lt;ItemGroup&gt;</span>
<a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-11" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-11" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-11"></a><span class="w">    </span><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;AutoFixture&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;4.17.0&quot;</span><span class="w"> </span><span class="nt">/&gt;</span>
<a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-12" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-12" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-12"></a><span class="w">  </span><span class="nt">&lt;/ItemGroup&gt;</span>
<a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-13" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-13" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-13"></a>
<a id="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-14" name="rest_code_f08cc48954da4007ab1cd6ab092bfb4f-14" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f08cc48954da4007ab1cd6ab092bfb4f-14"></a><span class="nt">&lt;/Project&gt;</span>
</pre></div>
<p>The first <code class="docutils literal">&lt;PropertyGroup&gt;</code> specifies what our project is (Exe = something you can run), specifies the target framework (.NET 6.0 <a class="brackets" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-3" id="footnote-reference-3" role="doc-noteref"><span class="fn-bracket">[</span>3<span class="fn-bracket">]</span></a>), and enables a few opt-in features of C#. The second <code class="docutils literal">&lt;ItemGroup&gt;</code> was inserted when we installed AutoFixture.</p>
<p>We can now write a pointless program in C#. Here’s our new <code class="docutils literal">Program.cs</code>:</p>
<div class="code"><pre class="code csharp"><a id="rest_code_3d97641a75b0423c987f8f6fb0c78955-1" name="rest_code_3d97641a75b0423c987f8f6fb0c78955-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_3d97641a75b0423c987f8f6fb0c78955-1"></a><span class="k">using</span><span class="w"> </span><span class="nn">AutoFixture</span><span class="p">;</span>
<a id="rest_code_3d97641a75b0423c987f8f6fb0c78955-2" name="rest_code_3d97641a75b0423c987f8f6fb0c78955-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_3d97641a75b0423c987f8f6fb0c78955-2"></a><span class="kt">var</span><span class="w"> </span><span class="n">fixture</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">Fixture</span><span class="p">();</span>
<a id="rest_code_3d97641a75b0423c987f8f6fb0c78955-3" name="rest_code_3d97641a75b0423c987f8f6fb0c78955-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_3d97641a75b0423c987f8f6fb0c78955-3"></a><span class="kt">var</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">fixture</span><span class="p">.</span><span class="n">Create</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">();</span>
<a id="rest_code_3d97641a75b0423c987f8f6fb0c78955-4" name="rest_code_3d97641a75b0423c987f8f6fb0c78955-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_3d97641a75b0423c987f8f6fb0c78955-4"></a><span class="kt">var</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">fixture</span><span class="p">.</span><span class="n">Create</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">();</span>
<a id="rest_code_3d97641a75b0423c987f8f6fb0c78955-5" name="rest_code_3d97641a75b0423c987f8f6fb0c78955-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_3d97641a75b0423c987f8f6fb0c78955-5"></a><span class="kt">var</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">a</span><span class="p">;</span>
<a id="rest_code_3d97641a75b0423c987f8f6fb0c78955-6" name="rest_code_3d97641a75b0423c987f8f6fb0c78955-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_3d97641a75b0423c987f8f6fb0c78955-6"></a><span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="n">result</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="s">&quot;Math is working&quot;</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;Math is broken&quot;</span><span class="p">);</span>
</pre></div>
<p>(We could just use C#’s/.NET’s built-in random number generator, AutoFixture is complete overkill here—it’s meant for auto-generating test data, with support for arbitrary classes and other data structures, and we’re just getting two random ints here. I’m using AutoFixture for this example, because it’s simple to use and demonstrate, and because it gets us a transitive dependency.)</p>
<p>And now, we can run it:</p>
<div class="code"><pre class="code text"><a id="rest_code_d9dee65276204d4cbb9b65e089915241-1" name="rest_code_d9dee65276204d4cbb9b65e089915241-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_d9dee65276204d4cbb9b65e089915241-1"></a>$ dotnet run
<a id="rest_code_d9dee65276204d4cbb9b65e089915241-2" name="rest_code_d9dee65276204d4cbb9b65e089915241-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_d9dee65276204d4cbb9b65e089915241-2"></a>Math is working
</pre></div>
<p>If we want something that can be run outside of the project, and possibly without .NET installed on the system, we can use dotnet publish. The most basic scenario:</p>
<div class="code"><pre class="code text"><a id="rest_code_0ac8438553dd4f7baedc301fd45a9d61-1" name="rest_code_0ac8438553dd4f7baedc301fd45a9d61-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_0ac8438553dd4f7baedc301fd45a9d61-1"></a>$ dotnet publish
<a id="rest_code_0ac8438553dd4f7baedc301fd45a9d61-2" name="rest_code_0ac8438553dd4f7baedc301fd45a9d61-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_0ac8438553dd4f7baedc301fd45a9d61-2"></a>$ ls bin/Debug/net6.0/publish
<a id="rest_code_0ac8438553dd4f7baedc301fd45a9d61-3" name="rest_code_0ac8438553dd4f7baedc301fd45a9d61-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_0ac8438553dd4f7baedc301fd45a9d61-3"></a>AutoFixture.dll*  Fare.dll*  mydotnetproject*  mydotnetproject.deps.json  mydotnetproject.dll  mydotnetproject.pdb  mydotnetproject.runtimeconfig.json
<a id="rest_code_0ac8438553dd4f7baedc301fd45a9d61-4" name="rest_code_0ac8438553dd4f7baedc301fd45a9d61-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_0ac8438553dd4f7baedc301fd45a9d61-4"></a>$ du -h bin/Debug/net6.0/publish
<a id="rest_code_0ac8438553dd4f7baedc301fd45a9d61-5" name="rest_code_0ac8438553dd4f7baedc301fd45a9d61-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_0ac8438553dd4f7baedc301fd45a9d61-5"></a>424K    bin/Debug/net6.0/publish
<a id="rest_code_0ac8438553dd4f7baedc301fd45a9d61-6" name="rest_code_0ac8438553dd4f7baedc301fd45a9d61-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_0ac8438553dd4f7baedc301fd45a9d61-6"></a>$ bin/Debug/net6.0/publish/mydotnetproject
<a id="rest_code_0ac8438553dd4f7baedc301fd45a9d61-7" name="rest_code_0ac8438553dd4f7baedc301fd45a9d61-7" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_0ac8438553dd4f7baedc301fd45a9d61-7"></a>Math is working
</pre></div>
<p>You can see that we’ve got a few files related to our project, as well as <code class="docutils literal">AutoFixture.dll</code> and <code class="docutils literal">Fare.dll</code>, which are our dependencies (<code class="docutils literal">Fare.dll</code> is a dependency of <code class="docutils literal">AutoFixture.dll</code>). Now, let’s try to remove <code class="docutils literal">AutoFixture.dll</code> from the published distribution:</p>
<div class="code"><pre class="code text"><a id="rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-1" name="rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-1"></a>$ rm bin/Debug/net6.0/publish/AutoFixture.dll
<a id="rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-2" name="rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-2"></a>$ bin/Debug/net6.0/publish/mydotnetproject
<a id="rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-3" name="rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-3"></a>Unhandled exception. System.IO.FileNotFoundException: Could not load file or assembly &#39;AutoFixture, Version=4.17.0.0, Culture=neutral, PublicKeyToken=b24654c590009d4f&#39;. The system cannot find the file specified.
<a id="rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-4" name="rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-4"></a>
<a id="rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-5" name="rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-5"></a>File name: &#39;AutoFixture, Version=4.17.0.0, Culture=neutral, PublicKeyToken=b24654c590009d4f&#39;
<a id="rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-6" name="rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_0a8ff1b606084dffa7e66c2b7f31fb5a-6"></a>[1]    45060 IOT instruction (core dumped)  bin/Debug/net6.0/publish/mydotnetproject
</pre></div>
<p>We can also try a more advanced scenario:</p>
<div class="code"><pre class="code text"><a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-1" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-1"></a>$ rm -rf bin obj  # clean up, just in case
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-2" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-2"></a>$ dotnet publish --sc -r linux-x64 -p:PublishSingleFile=true -o myoutput
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-3" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-3"></a>Microsoft (R) Build Engine version 17.0.1+b177f8fa7 for .NET
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-4" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-4"></a>Copyright (C) Microsoft Corporation. All rights reserved.
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-5" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-5"></a>
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-6" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-6"></a>  Determining projects to restore...
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-7" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-7" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-7"></a>  Restored /tmp/mydotnetproject/mydotnetproject.csproj (in 4.09 sec).
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-8" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-8" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-8"></a>  mydotnetproject -&gt; /tmp/mydotnetproject/bin/Debug/net6.0/linux-x64/mydotnetproject.dll
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-9" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-9" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-9"></a>  mydotnetproject -&gt; /tmp/mydotnetproject/myoutput/
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-10" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-10" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-10"></a>$ ls myoutput
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-11" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-11" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-11"></a>mydotnetproject*  mydotnetproject.pdb
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-12" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-12" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-12"></a>$ myoutput/mydotnetproject
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-13" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-13" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-13"></a>Math is working
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-14" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-14" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-14"></a>$ du -h myoutput/*
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-15" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-15" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-15"></a>62M     myoutput/mydotnetproject
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-16" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-16" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-16"></a>12K     myoutput/mydotnetproject.pdb
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-17" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-17" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-17"></a>$ file -k myoutput/mydotnetproject
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-18" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-18" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-18"></a>myoutput/mydotnetproject: ELF 64-bit LSB pie executable, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=47637c667797007d777f4322729d89e7fa53a870, for GNU/Linux 2.6.32, stripped, too many notes (256)\012- data
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-19" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-19" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-19"></a>$ file -k myoutput/mydotnetproject.pdb
<a id="rest_code_f3c7156ebb3d43e2afd22d946c03183c-20" name="rest_code_f3c7156ebb3d43e2afd22d946c03183c-20" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_f3c7156ebb3d43e2afd22d946c03183c-20"></a>myoutput/mydotnetproject.pdb: Microsoft Roslyn C# debugging symbols version 1.0\012- data
</pre></div>
<p>We have a single output file that contains our program, its dependencies, and parts of the .NET runtime. We also get debugging symbols if we want to run our binary with a .NET debugger and see the associated source code. (There are ways to make the binary file smaller, and we can move most arguments of <code class="docutils literal">dotnet publish</code> to the .csproj file, but this post is about Python, not .NET, so I’m not going to focus on them too much.)</p>
<section id="how-is-net-better-than-python">
<h3>How is .NET better than Python?</h3>
<p>I’m not going to bore you with the same demonstrations I’ve already shown when discussing <a class="reference internal" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#how-is-node-better-than-python">How is Node better than Python?</a>, but:</p>
<ul class="simple">
<li><p>You can run built .NET projects as any user, from anywhere in the filesystem.</p></li>
<li><p>All you need to run your code is the output directory (publishing is optional, but useful to have a cleaner output, to simplify deployment, and to possibly enable compilation to native code).</p></li>
<li><p>If you do publish in single-executable mode, you can just distribute the single executable, and your users don’t even need to have .NET installed.</p></li>
<li><p>You do not need to manage environments, you do not need special tools to run your code, you do not need to think about the current working directory when running code.</p></li>
</ul>
</section>
<section id="other-packaging-topics-1">
<h3>Other packaging topics</h3>
<p>Locking dependencies is disabled by default, but if you add <code class="docutils literal"><span class="pre">&lt;RestorePackagesWithLockFile&gt;true&lt;/RestorePackagesWithLockFile&gt;</span></code> to the <code class="docutils literal">&lt;PropertyGroup&gt;</code> in your <code class="docutils literal">.csproj</code> file, you can enable it (and get a <code class="docutils literal">packages.lock.json</code> file in output).</p>
<p>Regarding <a class="reference external" href="https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools">command line tools</a>, .NET has support for those as well. They can be installed globally or locally, and may be accessed via $PATH or via the <code class="docutils literal">dotnet</code> command.</p>
<p>As for publishing your package to NuGet.org or to another repository, you might want to look at the <a class="reference external" href="https://learn.microsoft.com/en-us/nuget/quickstart/create-and-publish-a-package-using-the-dotnet-cli">full docs</a> for more details, but the short version is:</p>
<ol class="arabic simple">
<li><p>Add some metadata to the <code class="docutils literal">.csproj</code> file (e.g. <code class="docutils literal">PackageId</code> and <code class="docutils literal">Version</code>)</p></li>
<li><p>Run <code class="docutils literal">dotnet pack</code> to get a <code class="docutils literal">.nupkg</code> file</p></li>
<li><p>Run <code class="docutils literal">dotnet nuget push</code> to upload the <code class="docutils literal">.nupkg</code> file (passing the file name and an API key)</p></li>
</ol>
<p>Once again, everything is done with a single <code class="docutils literal">dotnet</code> tool. The .NET IDEs (in particular, Visual Studio and Rider) do offer friendly GUI versions of many features. Some of those GUIs might be doings things slightly differently behind the scenes, but this is transparent to the user (and the backend is still MSBuild or a close derivative of it). I can take a CLI-created project, add a dependency from Rider, and publish an executable from VS, and everything will work the same. And perhaps XML files aren’t as cool as TOML, but they’re still easy to work with in this case.</p>
</section>
</section>
<section id="other-languages-and-ecosystems">
<h2>Other languages and ecosystems</h2>
<p>While we have explored two tools for two languages in depth, there are also other languages that deserve at least a mention. In the <strong>Java</strong> world, the two most commonly used tools are Maven and Gradle. Both tools can be used to manage dependencies and build artifacts that can be executed or distributed further (things like JAR files). Other tools with support for building Java projects exist, but most people just pick one of the two. The community of <strong>Scala</strong>, which is another JVM-based language, prefers sbt (which can be used for plain Java as well), but there are also Maven or Gradle users in that community. Finally, two new-ish languages which are quite popular in the recent times, <strong>Go</strong> and <strong>Rust</strong>, have first-party tooling integrated with the rest of the toolchain. The <code class="docutils literal">go</code> command-line tool can accomplish many build/dependency/packaging tasks. Rust’s <code class="docutils literal">cargo</code>, which ships with the standard distribution of Rust, handles dependencies, builds, running code and tests, as well as publishing your stuff to a registry.</p>
</section>
<section id="are-those-ecosystems-tools-perfect">
<h2>Are those ecosystems’ tools perfect?</h2>
<p>Not always, they have their deficiencies as well. In the Node ecosystem, packages may execute arbitrary code on install, which can be a security risk (there are some known examples, like a npm package <a class="reference external" href="https://arstechnica.com/information-technology/2022/03/sabotage-code-added-to-popular-npm-package-wiped-files-in-russia-and-belarus/">wiping hard drives in Russia and Belarus</a>, or another one <a href="https://arstechnica.com/information-technology/2018/11/hacker-backdoors-widely-used-open-source-software-to-steal-bitcoin/">stealing <s>imaginary Internet money</s> Bitcoin</a>). Binary packages are not distributed on the npm registry directly, they’re either built with <code class="docutils literal"><span class="pre">node-gyp</span></code>, or have prebuilt packages downloaded via <code class="docutils literal"><span class="pre">node-pre-gyp</span></code> (which is a third-party tool).</p>
<p>In the .NET ecosystem, the tools also create an <code class="docutils literal">obj</code> directory with temporary files. Those temporary files are tied to the environment they’re running in, and while the tooling will usually re-create them if something changes, it can sometimes fail and leave you with confusing errors (which can generally be solved by removing the <code class="docutils literal">bin</code> and <code class="docutils literal">obj</code> directories). If a package depends on native code (which is not already available on the target OS as part of a shared library), it must include binary builds in the NuGet package for all the platforms it supports, as there is <a class="reference external" href="https://github.com/NuGet/Home/issues/9631">no standard way</a> to allow building something from source.</p>
<p>You can also find deficiencies in the tools for the other languages mentioned. Some people think Maven is terrible because it uses XML and Gradle is the way to go, and others think Gradle’s use of a Groovy-based DSL makes things much harder than they need to be and prefer Maven instead.</p>
</section>
</section>
<section id="pep-582-the-future-of-python-packaging">
<h1>PEP 582: the future of Python packaging?</h1>
<p>Recall that when introducing PDM, I mentioned <a class="reference external" href="https://peps.python.org/pep-0582/">PEP 582</a>. This PEP defines a <code class="docutils literal">__pypackages__</code> directory. This directory would be taken into consideration by Python when looking for imports. It would behave similarly to <code class="docutils literal">node_modules</code>. Since there will be no symlinks to the system Python, it will resolve the issues with moving the virtual environment. Because the packages live in the project, there is no problem with sharing a project directory between multiple system users. It might even be possible for different computers (but with the same Python version and OS) to share the <code class="docutils literal">__pypackages__</code> directory (in some specific cases). The proposed <code class="docutils literal">__pypackages__</code> directory structure has <code class="docutils literal"><span class="pre">lib/python3.10/site-packages/</span></code> subfolders, which still makes the “reinstall on Python upgrade” step mandatory, but it doesn’t apply to minor version upgrades, and if you’re dealing with a pure-Python dependency tree, <code class="docutils literal">mv __pypackages__/lib/python3.10 __pypackages__/lib/python3.11</code> might just work. This structure does make sense for binary dependencies, or for dependencies necessary only on older Python versions, as it allows you to use multiple Python versions with the same project directory. The PEP does not say anything about sharing <code class="docutils literal">__pypackages__</code> between projects, but you could probably solve that problem with symlinks (assuming the tooling doesn’t care if the directory is a symlink, and it shouldn’t care IMO).</p>
<p>While PEP 582 is a great vision, and it would simplify many package-related workflows, it hasn’t seen much care from the powers-that-be. The PEP was proposed in May 2018, and there’s even <a class="reference external" href="https://github.com/kushaldas/pep582/blob/main/pep582.py">a usable implementation</a> that’s less than 50 lines of code, there <a class="reference external" href="https://discuss.python.org/t/pep-582-python-local-packages-directory/963/">hasn’t been much progress</a> on having it accepted and implemented in Python proper. However, PDM does not care, and it allows you to enable the future on your own machine.</p>
<section id="enabling-the-future-on-your-own-machine">
<h2>Enabling the future on your own machine</h2>
<p>Let’s enable the future on my own machine. That will require one simple command:</p>
<div class="code"><pre class="code text"><a id="rest_code_57dd2fcf65b6410d89b4d31b34f42435-1" name="rest_code_57dd2fcf65b6410d89b4d31b34f42435-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_57dd2fcf65b6410d89b4d31b34f42435-1"></a>$ eval &quot;$(pdm --pep582)&quot;
</pre></div>
<p>After that, we can initialize our project and install requests into it. Let’s try:</p>
<div class="code"><pre class="code text"><a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-1" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-1"></a>$ mkdir mypdmproject
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-2" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-2"></a>$ cd mypdmproject
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-3" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-3"></a>$ pdm init
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-4" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-4"></a>Creating a pyproject.toml for PDM...
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-5" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-5"></a>Please enter the Python interpreter to use
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-6" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-6"></a>0. /usr/bin/python (3.11)
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-7" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-7" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-7"></a>1. /usr/bin/python3.11 (3.11)
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-8" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-8" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-8"></a>2. /usr/bin/python2.7 (2.7)
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-9" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-9" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-9"></a>Please select (0): 1
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-10" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-10" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-10"></a>Using Python interpreter: /usr/bin/python3.11 (3.11)
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-11" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-11" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-11"></a>Would you like to create a virtualenv with /usr/bin/python3.11? [y/n] (y): n
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-12" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-12" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-12"></a>You are using the PEP 582 mode, no virtualenv is created.
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-13" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-13" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-13"></a>For more info, please visit https://peps.python.org/pep-0582/
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-14" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-14" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-14"></a>Is the project a library that will be uploaded to PyPI [y/n] (n): n
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-15" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-15" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-15"></a>License(SPDX name) (MIT):
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-16" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-16" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-16"></a>Author name (Chris Warrick):
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-17" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-17" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-17"></a>Author email (…):
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-18" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-18" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-18"></a>Python requires(&#39;*&#39; to allow any) (&gt;=3.11):
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-19" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-19" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-19"></a>Changes are written to pyproject.toml.
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-20" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-20" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-20"></a>$ ls
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-21" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-21" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-21"></a>pyproject.toml
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-22" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-22" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-22"></a>$ pdm add requests
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-23" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-23" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-23"></a>Adding packages to default dependencies: requests
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-24" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-24" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-24"></a>🔒 Lock successful
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-25" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-25" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-25"></a>Changes are written to pdm.lock.
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-26" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-26" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-26"></a>Changes are written to pyproject.toml.
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-27" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-27" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-27"></a>Synchronizing working set with lock file: 5 to add, 0 to update, 0 to remove
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-28" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-28" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-28"></a>
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-29" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-29" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-29"></a>  ✔ Install charset-normalizer 2.1.1 successful
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-30" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-30" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-30"></a>  ✔ Install certifi 2022.12.7 successful
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-31" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-31" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-31"></a>  ✔ Install idna 3.4 successful
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-32" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-32" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-32"></a>  ✔ Install requests 2.28.1 successful
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-33" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-33" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-33"></a>  ✔ Install urllib3 1.26.13 successful
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-34" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-34" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-34"></a>
<a id="rest_code_8bf4eb95599e4a33a6173a9a786fd022-35" name="rest_code_8bf4eb95599e4a33a6173a9a786fd022-35" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_8bf4eb95599e4a33a6173a9a786fd022-35"></a>🎉 All complete!
</pre></div>
<p>So far, so good (I’m not a fan of emoji in terminals, but that’s my only real complaint here.) Our <code class="docutils literal">pyproject.toml</code> looks like this:</p>
<div class="code"><pre class="code toml"><a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-1" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-1"></a><span class="k">[tool.pdm]</span>
<a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-2" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-2"></a>
<a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-3" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-3"></a><span class="k">[project]</span>
<a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-4" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-4"></a><span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;&quot;</span>
<a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-5" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-5"></a><span class="n">version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;&quot;</span>
<a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-6" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-6"></a><span class="n">description</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;&quot;</span>
<a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-7" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-7" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-7"></a><span class="n">authors</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span>
<a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-8" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-8" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-8"></a><span class="w">    </span><span class="p">{</span><span class="n">name</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s2">&quot;Chris Warrick&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">email</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s2">&quot;…&quot;</span><span class="p">},</span>
<a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-9" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-9" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-9"></a><span class="p">]</span>
<a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-10" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-10" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-10"></a><span class="n">dependencies</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span>
<a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-11" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-11" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-11"></a><span class="w">    </span><span class="s2">&quot;requests&gt;=2.28.1&quot;</span><span class="p">,</span>
<a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-12" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-12" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-12"></a><span class="p">]</span>
<a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-13" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-13" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-13"></a><span class="n">requires-python</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;&gt;=3.11&quot;</span>
<a id="rest_code_05fafc9392584b41a4c7d09ebcf57aee-14" name="rest_code_05fafc9392584b41a4c7d09ebcf57aee-14" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_05fafc9392584b41a4c7d09ebcf57aee-14"></a><span class="n">license</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="n">text</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s2">&quot;MIT&quot;</span><span class="p">}</span>
</pre></div>
<p>If we try to look into our file structure, we have this:</p>
<div class="code"><pre class="code text"><a id="rest_code_dfba376b40f84648b2865a0745760a19-1" name="rest_code_dfba376b40f84648b2865a0745760a19-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_dfba376b40f84648b2865a0745760a19-1"></a>$ ls
<a id="rest_code_dfba376b40f84648b2865a0745760a19-2" name="rest_code_dfba376b40f84648b2865a0745760a19-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_dfba376b40f84648b2865a0745760a19-2"></a>pdm.lock  __pypackages__/  pyproject.toml
<a id="rest_code_dfba376b40f84648b2865a0745760a19-3" name="rest_code_dfba376b40f84648b2865a0745760a19-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_dfba376b40f84648b2865a0745760a19-3"></a>$ ls __pypackages__
<a id="rest_code_dfba376b40f84648b2865a0745760a19-4" name="rest_code_dfba376b40f84648b2865a0745760a19-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_dfba376b40f84648b2865a0745760a19-4"></a>3.11/
<a id="rest_code_dfba376b40f84648b2865a0745760a19-5" name="rest_code_dfba376b40f84648b2865a0745760a19-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_dfba376b40f84648b2865a0745760a19-5"></a>$ ls __pypackages__/3.11
<a id="rest_code_dfba376b40f84648b2865a0745760a19-6" name="rest_code_dfba376b40f84648b2865a0745760a19-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_dfba376b40f84648b2865a0745760a19-6"></a>bin/  include/  lib/
<a id="rest_code_dfba376b40f84648b2865a0745760a19-7" name="rest_code_dfba376b40f84648b2865a0745760a19-7" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_dfba376b40f84648b2865a0745760a19-7"></a>$ ls __pypackages__/3.11/lib
<a id="rest_code_dfba376b40f84648b2865a0745760a19-8" name="rest_code_dfba376b40f84648b2865a0745760a19-8" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_dfba376b40f84648b2865a0745760a19-8"></a>certifi/             certifi-2022.12.7.dist-info/
<a id="rest_code_dfba376b40f84648b2865a0745760a19-9" name="rest_code_dfba376b40f84648b2865a0745760a19-9" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_dfba376b40f84648b2865a0745760a19-9"></a>idna/                idna-3.4.dist-info/
<a id="rest_code_dfba376b40f84648b2865a0745760a19-10" name="rest_code_dfba376b40f84648b2865a0745760a19-10" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_dfba376b40f84648b2865a0745760a19-10"></a>charset_normalizer/  charset_normalizer-2.1.1.dist-info/
<a id="rest_code_dfba376b40f84648b2865a0745760a19-11" name="rest_code_dfba376b40f84648b2865a0745760a19-11" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_dfba376b40f84648b2865a0745760a19-11"></a>requests/            requests-2.28.1.dist-info/
<a id="rest_code_dfba376b40f84648b2865a0745760a19-12" name="rest_code_dfba376b40f84648b2865a0745760a19-12" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_dfba376b40f84648b2865a0745760a19-12"></a>urllib3/             urllib3-1.26.13.dist-info/
</pre></div>
<p>We’ll write a simple Python program (let’s call it <code class="docutils literal">mypdmproject.py</code>) that makes a HTTP request using <code class="docutils literal">requests</code>. It will also print <code class="docutils literal">requests.__file__</code> so we’re sure it isn’t using some random system copy: <a class="brackets" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-4" id="footnote-reference-4" role="doc-noteref"><span class="fn-bracket">[</span>4<span class="fn-bracket">]</span></a></p>
<div class="code"><pre class="code python"><a id="rest_code_88da4613f9c142128800d8f4917c7e96-1" name="rest_code_88da4613f9c142128800d8f4917c7e96-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_88da4613f9c142128800d8f4917c7e96-1"></a><span class="kn">import</span><span class="w"> </span><span class="nn">requests</span>
<a id="rest_code_88da4613f9c142128800d8f4917c7e96-2" name="rest_code_88da4613f9c142128800d8f4917c7e96-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_88da4613f9c142128800d8f4917c7e96-2"></a><span class="nb">print</span><span class="p">(</span><span class="n">requests</span><span class="o">.</span><span class="vm">__file__</span><span class="p">)</span>
<a id="rest_code_88da4613f9c142128800d8f4917c7e96-3" name="rest_code_88da4613f9c142128800d8f4917c7e96-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_88da4613f9c142128800d8f4917c7e96-3"></a><span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;https://chriswarrick.com/&quot;</span><span class="p">)</span>
<a id="rest_code_88da4613f9c142128800d8f4917c7e96-4" name="rest_code_88da4613f9c142128800d8f4917c7e96-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_88da4613f9c142128800d8f4917c7e96-4"></a><span class="nb">print</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">text</span><span class="p">[:</span><span class="mi">15</span><span class="p">])</span>
</pre></div>
<div class="code"><pre class="code text"><a id="rest_code_d287b0c578984663a2928bb41455147e-1" name="rest_code_d287b0c578984663a2928bb41455147e-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_d287b0c578984663a2928bb41455147e-1"></a>$ python mypdmproject.py
<a id="rest_code_d287b0c578984663a2928bb41455147e-2" name="rest_code_d287b0c578984663a2928bb41455147e-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_d287b0c578984663a2928bb41455147e-2"></a>/tmp/mypdmproject/__pypackages__/3.11/lib/requests/__init__.py
<a id="rest_code_d287b0c578984663a2928bb41455147e-3" name="rest_code_d287b0c578984663a2928bb41455147e-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_d287b0c578984663a2928bb41455147e-3"></a>&lt;!DOCTYPE html&gt;
</pre></div>
<p>Let’s finally try the tests we’ve done in the other languages. Requests is useless without urllib3, so let’s remove it <a class="brackets" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-5" id="footnote-reference-5" role="doc-noteref"><span class="fn-bracket">[</span>5<span class="fn-bracket">]</span></a> and see how well it works.</p>
<div class="code"><pre class="code text"><a id="rest_code_025901aac58247a4a73a4e28c999ae5d-1" name="rest_code_025901aac58247a4a73a4e28c999ae5d-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_025901aac58247a4a73a4e28c999ae5d-1"></a>$ rm -rf __pypackages__/3.11/lib/urllib3*
<a id="rest_code_025901aac58247a4a73a4e28c999ae5d-2" name="rest_code_025901aac58247a4a73a4e28c999ae5d-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_025901aac58247a4a73a4e28c999ae5d-2"></a>$ python mypdmproject.py
<a id="rest_code_025901aac58247a4a73a4e28c999ae5d-3" name="rest_code_025901aac58247a4a73a4e28c999ae5d-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_025901aac58247a4a73a4e28c999ae5d-3"></a>Traceback (most recent call last):
<a id="rest_code_025901aac58247a4a73a4e28c999ae5d-4" name="rest_code_025901aac58247a4a73a4e28c999ae5d-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_025901aac58247a4a73a4e28c999ae5d-4"></a>  File &quot;/tmp/mypdmproject/mypdmproject.py&quot;, line 1, in &lt;module&gt;
<a id="rest_code_025901aac58247a4a73a4e28c999ae5d-5" name="rest_code_025901aac58247a4a73a4e28c999ae5d-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_025901aac58247a4a73a4e28c999ae5d-5"></a>    import requests
<a id="rest_code_025901aac58247a4a73a4e28c999ae5d-6" name="rest_code_025901aac58247a4a73a4e28c999ae5d-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_025901aac58247a4a73a4e28c999ae5d-6"></a>  File &quot;/tmp/mypdmproject/__pypackages__/3.11/lib/requests/__init__.py&quot;, line 43, in &lt;module&gt;
<a id="rest_code_025901aac58247a4a73a4e28c999ae5d-7" name="rest_code_025901aac58247a4a73a4e28c999ae5d-7" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_025901aac58247a4a73a4e28c999ae5d-7"></a>    import urllib3
<a id="rest_code_025901aac58247a4a73a4e28c999ae5d-8" name="rest_code_025901aac58247a4a73a4e28c999ae5d-8" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_025901aac58247a4a73a4e28c999ae5d-8"></a>ModuleNotFoundError: No module named &#39;urllib3&#39;
</pre></div>
<p>Finally, can we try with a different directory? How about a different user?</p>
<div class="code"><pre class="code text"><a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-1" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-1" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-1"></a>$ pdm install
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-2" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-2" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-2"></a>Synchronizing working set with lock file: 1 to add, 0 to update, 0 to remove
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-3" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-3" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-3"></a>
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-4" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-4" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-4"></a>  ✔ Install urllib3 1.26.13 successful
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-5" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-5" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-5"></a>
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-6" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-6" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-6"></a>🎉 All complete!
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-7" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-7" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-7"></a>$ pwd
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-8" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-8" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-8"></a>/tmp/mypdmproject
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-9" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-9" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-9"></a>$ cd ~
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-10" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-10" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-10"></a>$ python /tmp/mypdmproject/mypdmproject.py
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-11" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-11" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-11"></a>/tmp/mypdmproject/__pypackages__/3.11/lib/requests/__init__.py
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-12" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-12" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-12"></a>&lt;!DOCTYPE html&gt;
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-13" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-13" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-13"></a># su -s /bin/bash -c &#39;eval &quot;$(/tmp/pdmvenv/bin/pdm --pep582 bash)&quot;; python /tmp/mypdmproject/mypdmproject.py&#39; - nobody
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-14" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-14" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-14"></a>su: warning: cannot change directory to /nonexistent: No such file or directory
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-15" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-15" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-15"></a>/tmp/mypdmproject/__pypackages__/3.11/lib/requests/__init__.py
<a id="rest_code_9544e824a8e84543b9a705b203b8f3f0-16" name="rest_code_9544e824a8e84543b9a705b203b8f3f0-16" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#rest_code_9544e824a8e84543b9a705b203b8f3f0-16"></a>&lt;!DOCTYPE html&gt;
</pre></div>
<p>This is looking pretty good. An independent project manages to do what the big Authority failed to do over so many years.</p>
</section>
<section id="is-this-the-perfect-thing">
<h2>Is this the perfect thing?</h2>
<p>Well, almost. There are two things that I have complaints about. The first one is the <code class="docutils literal">pdm <span class="pre">--pep582</span></code> hack, but hopefully, the PyPA gets its act together and gets it into Python core soon. However, another important problem is the lack of separation from system site-packages. Avid readers of footnotes <a class="brackets" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-6" id="footnote-reference-6" role="doc-noteref"><span class="fn-bracket">[</span>6<span class="fn-bracket">]</span></a> might have noticed I had to use a Docker container in my PDM experiments, because requests is very commonly found in system <code class="docutils literal"><span class="pre">site-packages</span></code> (especially when using system Pythons, which have requests because of some random package, or because it was unbundled from pip). <a class="brackets" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-7" id="footnote-reference-7" role="doc-noteref"><span class="fn-bracket">[</span>7<span class="fn-bracket">]</span></a> This can break things in ways you don’t expect, because you might end up importing and depending on system-wide things, or mixing system-wide and local packages (if you don’t install an extra requirement, but those packages are present system-wide, then you might end up using an extra you haven’t asked for). This is an important problem—a good solution would be to disable system site-packages if a <code class="docutils literal">__pypackages__</code> directory is in use.</p>
</section>
<section id="the-part-where-the-steering-council-kills-it">
<h2>The part where the Steering Council kills it</h2>
<p>In late March 2023, the Python Steering Council has announced <a class="reference external" href="https://discuss.python.org/t/pep-582-python-local-packages-directory/963/430">the rejection of PEP 582</a>. The reasons cited in the SC decision cited the limitations of the PEP (the <code class="docutils literal">__pypackages__</code> directory not always being enough, and the lack of specification on how it would behave in edge cases). Another argument is that it is possible to get <code class="docutils literal">__pypackages__</code> via “one of the many existing customization mechanisms for <code class="docutils literal">sys.path</code>, like <code class="docutils literal">.pth</code> files or a <code class="docutils literal">sitecustomize</code> module” — things commonly considered hacks, not real solutions. While users certainly can do anything they want to the <code class="docutils literal">sys.path</code> (often with tragic consequences), the point of having a common standard is to encourage tools to add support for it — if you use the aforementioned hacks, your IDE might end up not noticing the packages or considering them part of your code (trying to index them and search for things in them). Another reason cited for the rejection is the disagreement among the packaging community, which should not be surprising, especially in light of the next section.</p>
<p>The PEP 582/<code class="docutils literal">__pypackages__</code> mechanism may become official one day, and finally make Python packaging approachable. That would probably require someone to step up and write a new PEP that would make more people happy. Or Python might be stuck with all these incompatible tools, and invent 10 more in the next few years. (PDM is still there, and it still supports <code class="docutils literal">__pypackages__</code>, even though its implementation isn’t exactly the same as suggested by the now-rejected PEP.) Python’s current trajectory, as demonstrated by this decision, and by many people still being forced to struggle with the needlessly complicated virtual environments, sounds an awful lot like <a class="reference external" href="https://en.wikipedia.org/wiki/%27No_Way_to_Prevent_This,%27_Says_Only_Nation_Where_This_Regularly_Happens">the classic Onion headline</a>: ‘No Way to Prevent This,’ Says Only Programming Community Where This Regularly Happens.</p>
</section>
</section>
<section id="pypa-versus-reality-packaging-survey-results-and-pypa-reaction">
<h1>PyPA versus reality: packaging survey results and PyPA reaction</h1>
<p>Some time ago, the PSF ran a survey on packaging. Over 8000 people responded. <a class="reference external" href="https://drive.google.com/file/d/1U5d5SiXLVkzDpS0i1dJIA4Hu5Qg704T9/view">The users have spoken:</a></p>
<ul class="simple">
<li><p>Most people think packaging is too complex.</p></li>
<li><p>An overwhelming majority prefers using just a single tool.</p></li>
<li><p>Most people also think the existence of multiple tools is not beneficial for the Python packaging ecosystem.</p></li>
<li><p>Virtually everyone would prefer a clearly defined official workflow.</p></li>
<li><p>Over 50% of responses think tools for other ecosystems are better at managing dependencies and installing packages.</p></li>
</ul>
<p>The next step after this survey was for the packaging community to <a class="reference external" href="https://discuss.python.org/t/python-packaging-strategy-discussion-part-1/22420">discuss its results</a> and try to come up with a new packaging strategy. The first post from Shamika Mohanan (the Packaging Project Manager at PSF) that triggered the discussion also focused heavily on the users’ vision to unify packaging tools and to have One True Tool. This discussion was open to people involved with the packaging world; many participants of the discussion are involved with PyPA, and I don’t think I’ve seen a single comment from the people behind Poetry or PDM.</p>
<p>Most of the thread ended up being discussion of binary extensions, including discussions of how to help tool proliferation by making it possible for tools that aren’t setuptools to build binary extensions. There was also a lot of focus on the scientific community’s issues with <a class="reference external" href="https://pypackaging-native.github.io/">libraries with native code</a>, heavily rooted in C/C++, and with attempts to replace Conda with new PyPA-approved tools. The “unified tool” for everyone else was mentioned in some posts, but they were certainly the minority.</p>
<p>Some PyPA members talked about a UX analysis, and that they expect the unified tool to be re-exporting functionality from existing tools—which immediately raises the question: which tools should it export functionality from and why? Is <code class="docutils literal">pip install <span class="pre">unified-packaging-tool</span></code> going to bring in all fourteen? Is the fact that users are unhappy with what they have, and many of them would be happy with something lke npm/dotnet/cargo, not enough to determine the UX direction of the unified tool?</p>
<p>Some of them are also against breaking existing workflows. Is a unified packaging tool going to work for every single user? Definitely not. But are there that many distinct basic workflows? If we ignore things that border on bikeshedding, such as src vs no-src, or venv locations, are there that many workflows to consider? Someone making a library and someone making an application do have different needs (e.g. with regard to publishing the package or acceptable dependency versions). Someone working with C extensions (or extensions using something like Cython) may have different needs, but their needs would usually be a superset of the needs of someone working on a pure-Python project. The scientific community might have more specialized needs, related to complex non-Python parts, but I am positive many of their points could be solved by the unified tool as well, even if it’s not by the time this tool reaches v1.0. It is also possible that the scientific community might prefer to stay with Conda, or with some evolution of it that brings it closer in line with the Unified Packaging Tool but also solves the scientists’ needs better than a tool also solving the non-scientists’ needs can.</p>
<p>Then there’s a discussion about the existing tools and which one is the tool for the future. The maintainer of Hatch (Ofek Lev) says that <a class="reference external" href="https://discuss.python.org/t/python-packaging-strategy-discussion-part-1/22420/4">Hatch can provide the “unified UX”</a>. But do the maintainers of Poetry or PDM agree? Poetry seems to be far more active than Hatch, going by GitHub issues, and it’s also worth noting that Hatch’s bus factor is 1 (with Ofek Lev responsible for 542 out of 576 commits to the master branch). <a class="reference external" href="https://discuss.python.org/t/python-packaging-strategy-discussion-part-1/22420/46">Russell Keith-Magee from BeeWare</a> has highlighted the fact that tooling aside, the PyPA does a bad job at communicating things. Russell mentioned that one of PyPA tutorials now uses Hatch, but there is no way to know if the PyPA considers Hatch to be the future, are people supposed to migrate onto Hatch, and is Flit, another recent PyPA tool, now useless? Russell also makes good points about focusing efforts: should people focus on helping Hatch support extension modules (which, according to the Hatch maintainer, is the last scenario requiring setuptools; other participants note that you can already build native code without setuptools), or should people focus on improving setuptools compatibility with PEP 517?</p>
<p>There were also some people stating their opinions on unifying things in various ways—and many of them are <a class="reference external" href="https://discuss.python.org/t/python-packaging-strategy-discussion-part-1/22420/136">against</a> <a class="reference external" href="https://discuss.python.org/t/python-packaging-strategy-discussion-part-1/22420/137">unifying</a> things. There were some voices of reason, like that of Russell Keith-Magee, or of <a class="reference external" href="https://discuss.python.org/t/python-packaging-strategy-discussion-part-1/22420/140">Simon Notley</a>, who correctly noticed the thread fails to resolve problems of developers, who are confused about packaging, and don’t understand the different choices available and how they interoperate. Simon does agree that native dependencies are important and happen often in Python projects (and so do I), but the users who responded to the survey had something else in mind — as exemplified by the discussion opening post, mentioning the user expecting the simplicity of Rust’s cargo, and by the survey results. 70% of the survey respondents also use <code class="docutils literal">npm</code>, so many Python users have already seen the simpler workflows. The survey respondents were also asked to rank a few focus areas based on importance. “Making Python packaging better serve common use cases and workflows” was ranked first out of the provided options <a class="brackets" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-8" id="footnote-reference-8" role="doc-noteref"><span class="fn-bracket">[</span>8<span class="fn-bracket">]</span></a> by 3248 participants. “Supporting a wider range of use cases (e.g. edge cases, etc.)” was ranked first by 379 people, and it was the least important in the minds of 2989 people.</p>
<p>One more point that highlights the detachment of packaging folk from reality was mentioned by Anderson Bravalheri. To Anderson, a new unified tool would be <a class="reference external" href="https://discuss.python.org/t/python-packaging-strategy-discussion-part-1/22420/133">disrespectful of the work</a> the maintainers of the existing tools put into maintaining them, and disrespectful of users who had to adapt to the packaging mess. This point is completely absurd. Was the replacement of MS-DOS/Windows 9x and Classic Mac OS with Windows NT and <s>Mac OS X</s> <s>OS X</s> macOS disrespectful to their respective designers, and the users who had to adapt to manually configuring minutiae, figuring out how to get all your software and hardware to run with weird limitations that were necessary in the 1980s, and the system crashing every once in a while? Was the replacement of horses with cars disrespectful to horses, and the people who were removing horse manure from the streets? Was the replacement of the Ford Model T with faster, safer, more efficient, and easier to use cars disrespectful to Henry Ford? Technology comes and goes, and sometimes, getting an improvement means we need to get rid of the old stuff. This applies outside of technology, too—you could come up with many examples of change in the world, which might have put some people out of power, but has greatly improved the lives of millions of people (the fall of communism in Europe, for example). Also, going back to the technology world of today, this sentiment suggests Anderson is far too attached to the software they write—is this a healthy approach?</p>
<p>Nobody raised PEP 582 or the complexity of virtual environments. It might not be visible from the ivory towers of packaging tool maintainers, who have years of experience dealing with them, but it certainly does exist for regular people, for people who think the Python provided by their Linux distro is good enough, and especially for people for whom Python is their introduction to programming.</p>
<p>I would like to once again highlight: that’s not just the opinion of one random rambling Chris. The opinion that Python packaging needs to be simplified and unified is held by about half of the 8774 people who took the survey.</p>
<p>But here’s one more interesting thing: Discourse, the platform that the discussion was held on, shows the number of times a link was clicked. Granted, this count might not be always accurate, but if we assume it is, the link to the results summary was clicked only 14 times (as of 2023-01-14 21:20 UTC). The discussion has 28 participants and 2.2k views. If we believe the link click counter, <strong>half of the discussion participants did not even bother reading what the people think</strong>.</p>
<img alt="/images/python-packaging/discourse-link-clicks.png" class="align-center" src="https://chriswarrick.com/images/python-packaging/discourse-link-clicks.png">
</section>
<section id="summary">
<h1>Summary</h1>
<p>Python packaging is a mess, and it always has been. There are tons of tools, mostly incompatible with each other, and no tool can solve <em>all</em> problems (especially no tool from the PyPA). PDM is really close to the ideal, since it can do away with the overhead of managing virtual environments—which is hopefully the future of Python packaging, or the 2010s of Node.js packaging (although it is not going to be the 2023 of Python packaging, considering the Steering Council rejection). Perhaps in a few years, Python developers (and more importantly, Python learners!) will be able to just <code class="docutils literal">pip install</code> (or <code class="docutils literal">pdm install</code>?) what they need, without worrying about some “virtual environment” thing, that is separate but not quite from a system Python, and that is not a virtual machine. Python needs less tools, not more.</p>
<p><a class="reference external" href="https://en.wikipedia.org/wiki/Carthago_delenda_est">Furthermore, I consider that the PyPA must be destroyed.</a> The strategy discussion highlights the fact that they are unable to make Python packaging work the way the users expect. The PyPA should focus on producing one good tool, and on getting PEP 582 into Python. A good way to achieve this would be to put its resources behind PDM. The issues with native code and binary wheels are important, but plain-Python workflows, or workflows with straightforward binary dependencies, are much more common, and need to be improved. This improvement needs to happen now.</p>
<p>Discuss in the comments below, on <a class="reference external" href="https://news.ycombinator.com/item?id=34390585">Hacker News</a>, or on <a class="reference external" href="https://www.reddit.com/r/Python/comments/10cnx5i/how_to_improve_python_packaging_or_why_fourteen/">Reddit</a>.</p>
</section>
<section id="footnotes">
<h1>Footnotes</h1>
<aside class="footnote-list brackets">
<aside class="footnote brackets" id="footnote-1" role="doc-footnote">
<span class="label"><span class="fn-bracket">[</span><a role="doc-backlink" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-reference-1">1</a><span class="fn-bracket">]</span></span>
<p>Funnily enough, the aphorism itself fails at “one obvious way to do it”. It is with dashes set in two different ways (with spaces after but not before, and with spaces before but not after), and none of them is the correct one (most English style guides prefer no spaces, but some allow spaces on both sides).</p>
</aside>
<aside class="footnote brackets" id="footnote-2" role="doc-footnote">
<span class="label"><span class="fn-bracket">[</span><a role="doc-backlink" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-reference-2">2</a><span class="fn-bracket">]</span></span>
<p>Apologies for the slight Linux focus of this post; all the points I make apply on Windows as well, but perhaps with some slightly different names and commands.</p>
</aside>
<aside class="footnote brackets" id="footnote-3" role="doc-footnote">
<span class="label"><span class="fn-bracket">[</span><a role="doc-backlink" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-reference-3">3</a><span class="fn-bracket">]</span></span>
<p>There’s a new major version of .NET every year, with the even-numbered versions being LTS. Those are far less revolutionary than the Python 2 → 3 transition, and after you jump on the modern .NET train, upgrading a project to the new major version is fairly simple (possibly even just bumping the version number).</p>
</aside>
<aside class="footnote brackets" id="footnote-4" role="doc-footnote">
<span class="label"><span class="fn-bracket">[</span><a role="doc-backlink" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-reference-4">4</a><span class="fn-bracket">]</span></span>
<p>And to be extra sure, I used a clean <code class="docutils literal">python:latest</code> Docker container, since requests is so commonly found in system site packages.</p>
</aside>
<aside class="footnote brackets" id="footnote-5" role="doc-footnote">
<span class="label"><span class="fn-bracket">[</span><a role="doc-backlink" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-reference-5">5</a><span class="fn-bracket">]</span></span>
<p>A little caveat here, I also had to remove the <code class="docutils literal"><span class="pre">dist-info</span></code> folder, so that PDM would know it needs to be reinstalled.</p>
</aside>
<aside class="footnote brackets" id="footnote-6" role="doc-footnote">
<span class="label"><span class="fn-bracket">[</span><a role="doc-backlink" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-reference-6">6</a><span class="fn-bracket">]</span></span>
<p>Yes, that’s you!</p>
</aside>
<aside class="footnote brackets" id="footnote-7" role="doc-footnote">
<span class="label"><span class="fn-bracket">[</span><a role="doc-backlink" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-reference-7">7</a><span class="fn-bracket">]</span></span>
<p>Also, why is there no good HTTP client library in Python’s standard library? Is the “standard library is where packages go to die” argument still relevant, if requests had four releases in 2022, and urllib3 had six, and most of the changes were minor?</p>
</aside>
<aside class="footnote brackets" id="footnote-8" role="doc-footnote">
<span class="label"><span class="fn-bracket">[</span><a role="doc-backlink" href="https://chriswarrick.com/blog/2023/01/15/how-to-improve-python-packaging/#footnote-reference-8">8</a><span class="fn-bracket">]</span></span>
<p>I have removed the “Other” option, and shifted all options ranked below it by one place, since we don’t know what the other thing was and how it related to the options presented (the free-form responses were removed from the public results spreadsheet to preserve the users’ anonymity). In the event a respondent left some of the options without a number, the blank options were not considered neither first nor last.</p>
</aside>
</aside>
</section>
<section id="revision-history">
<h1>Revision History</h1>
<p>This post got amended in April 2023 with an update about the SC rejection of PEP 582 (in a new subsection and in the Summary section).</p>
</section>
]]></content:encoded><category>Python</category><category>.NET</category><category>C#</category><category>JavaScript</category><category>Node.js</category><category>npm</category><category>packaging</category><category>PDM</category><category>pip</category><category>PyPA</category><category>Python</category><category>virtual environments</category></item><item><title>Writing a Console App in C# for Fun and Profit</title><dc:creator>Chris Warrick</dc:creator><link>https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/</link><pubDate>Mon, 19 Sep 2022 20:35:00 GMT</pubDate><guid>https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/</guid><description>
I recently wrote a simple console app in C#. This post tells you more about the language, the .NET ecosystem, and why you should write your next app in it.
</description><content:encoded><![CDATA[
<p>I recently wrote a simple console app in C#. This post tells you more about the language, the .NET ecosystem, and why you should write your next app in it.</p>



<section id="what-is-the-app">
<h1>What is the app?</h1>
<p>The app is called Gitco.NET. It’s basically a rewrite of a previous Ruby script. It shows a console menu with Git branches, allowing things like filtering and toggling the display of remote branches. Simple, but quite convenient for working with Git in a terminal. I rewrote it in C# for better Windows compatibility — but the new version works on Linux and macOS equally well, can be distributed as a single executable, and is also unit-tested.</p>
</section>
<section id="what-is-c">
<h1>What is C#?</h1>
<p>C# is a modern, high-level language designed by Microsoft in 2000, heavily inspired by (and competing with) Java.</p>
<section id="the-obligatory-hello-world-program">
<h2>The obligatory hello world program</h2>
<div class="code"><pre class="code csharp"><a id="rest_code_1c3ea5cfc3ca48d7a0e8d89503eb0ef0-1" name="rest_code_1c3ea5cfc3ca48d7a0e8d89503eb0ef0-1" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_1c3ea5cfc3ca48d7a0e8d89503eb0ef0-1"></a><span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&quot;Hello, world!&quot;</span><span class="p">);</span>
</pre></div>
<p>Just one line is enough. This program requires C# 10 and .NET 6, the latest versions of the language and the framework (and the implicit usings feature enabled).</p>
</section>
<section id="the-slightly-less-cool-version-of-the-hello-world-program">
<h2>The slightly less cool version of the hello world program</h2>
<div class="code"><pre class="code csharp"><a id="rest_code_571d56923a92460eb9ae1a7ce09e3cea-1" name="rest_code_571d56923a92460eb9ae1a7ce09e3cea-1" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_571d56923a92460eb9ae1a7ce09e3cea-1"></a><span class="k">using</span><span class="w"> </span><span class="nn">System</span><span class="p">;</span>
<a id="rest_code_571d56923a92460eb9ae1a7ce09e3cea-2" name="rest_code_571d56923a92460eb9ae1a7ce09e3cea-2" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_571d56923a92460eb9ae1a7ce09e3cea-2"></a>
<a id="rest_code_571d56923a92460eb9ae1a7ce09e3cea-3" name="rest_code_571d56923a92460eb9ae1a7ce09e3cea-3" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_571d56923a92460eb9ae1a7ce09e3cea-3"></a><span class="k">class</span><span class="w"> </span><span class="nc">Program</span>
<a id="rest_code_571d56923a92460eb9ae1a7ce09e3cea-4" name="rest_code_571d56923a92460eb9ae1a7ce09e3cea-4" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_571d56923a92460eb9ae1a7ce09e3cea-4"></a><span class="p">{</span>
<a id="rest_code_571d56923a92460eb9ae1a7ce09e3cea-5" name="rest_code_571d56923a92460eb9ae1a7ce09e3cea-5" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_571d56923a92460eb9ae1a7ce09e3cea-5"></a><span class="w">  </span><span class="k">static</span><span class="w"> </span><span class="k">void</span><span class="w"> </span><span class="nf">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span><span class="w"> </span><span class="n">args</span><span class="p">)</span>
<a id="rest_code_571d56923a92460eb9ae1a7ce09e3cea-6" name="rest_code_571d56923a92460eb9ae1a7ce09e3cea-6" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_571d56923a92460eb9ae1a7ce09e3cea-6"></a><span class="w">  </span><span class="p">{</span>
<a id="rest_code_571d56923a92460eb9ae1a7ce09e3cea-7" name="rest_code_571d56923a92460eb9ae1a7ce09e3cea-7" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_571d56923a92460eb9ae1a7ce09e3cea-7"></a><span class="w">    </span><span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&quot;Hello, world!&quot;</span><span class="p">);</span>
<a id="rest_code_571d56923a92460eb9ae1a7ce09e3cea-8" name="rest_code_571d56923a92460eb9ae1a7ce09e3cea-8" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_571d56923a92460eb9ae1a7ce09e3cea-8"></a><span class="w">  </span><span class="p">}</span>
<a id="rest_code_571d56923a92460eb9ae1a7ce09e3cea-9" name="rest_code_571d56923a92460eb9ae1a7ce09e3cea-9" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_571d56923a92460eb9ae1a7ce09e3cea-9"></a><span class="p">}</span>
</pre></div>
<p>We’ve got four lines of code (plus four lines of braces <a class="brackets" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#footnote-1" id="footnote-reference-1" role="doc-noteref"><span class="fn-bracket">[</span>1<span class="fn-bracket">]</span></a> ). We can see the <code class="docutils literal">using</code> directive to import everything from the <code class="docutils literal">System</code> namespace, the definition of a <code class="docutils literal">class</code>, a <code class="docutils literal">Main</code> method, and a call to <code class="docutils literal">Console.WriteLine</code>.</p>
<p>We’ll talk more about C# later, highlighting some of the cooler things seen in Gitco.NET.</p>
</section>
</section>
<section id="what-is-net">
<h1>What is .NET?</h1>
<p>The term “.NET” had quite a lot of meanings over the past two decades. Microsoft accounts were once called .NET Passport, and Windows Server 2003 was almost called “Windows Server .NET 2003”. Another thing called .NET was the .NET Framework. .NET Framework is a heavily integrated component of Windows, and it’s basically what was used to run C# (and F#, and VB.NET) — it includes the virtual machine (CLR, Core Language Runtime), a lot of libraries (Framework Class Library), and a lot of Windows-specific things (such as COM, Windows Forms, WPF).</p>
<p>Microsoft’s .NET Framework is proprietary and tied to Windows. An open-source, independent re-implementation of .NET is Mono. At one point, some GNOME apps were written in Mono and Gtk#. Mono was also used in Xamarin, which can be used to write Android and iOS apps in C#.</p>
<p>But then came out .NET Core, which is Microsoft’s open-source .NET with a new runtime (CoreCLR), new set of libraries (CoreFX), and multi-platform compatibility (Linux and macOS). After a few years, .NET Core got renamed to .NET (around the time it had pretty good feature parity with the classic .NET Framework). With the new .NET, you can build console apps, web apps (using ASP.NET Core, which is a pretty cool framework), mobile apps (soon using MAUI), and desktop apps (there are a few options).</p>
</section>
<section id="gitco-net-code-tour">
<h1>Gitco.NET code tour</h1>
<p>Let’s go on a little tour of the more interesting parts of the code.</p>
<section id="snippet-1">
<h2>Snippet 1</h2>
<div class="code"><pre class="code csharp"><a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-1" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-1" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-1"></a><span class="k">public</span><span class="w"> </span><span class="k">static</span><span class="w"> </span><span class="n">List</span><span class="o">&lt;</span><span class="n">Branch</span><span class="o">&gt;</span><span class="w"> </span><span class="n">ExtractBranchListFromGitOutput</span><span class="p">(</span><span class="kt">string</span><span class="w"> </span><span class="n">gitOutput</span><span class="p">)</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-2" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-2" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-2"></a><span class="w">  </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">gitOutput</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-3" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-3" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-3"></a><span class="w">    </span><span class="p">.</span><span class="n">TrimEnd</span><span class="p">()</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-4" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-4" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-4"></a><span class="w">    </span><span class="p">.</span><span class="n">ReplaceLineEndings</span><span class="p">(</span><span class="s">&quot;\n&quot;</span><span class="p">)</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-5" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-5" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-5"></a><span class="w">    </span><span class="p">.</span><span class="n">Split</span><span class="p">(</span><span class="sc">&#39;\n&#39;</span><span class="p">)</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-6" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-6" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-6"></a><span class="w">    </span><span class="p">.</span><span class="n">Select</span><span class="p">(</span><span class="n">branchLine</span><span class="w"> </span><span class="o">=&gt;</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-7" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-7" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-7"></a><span class="w">    </span><span class="p">{</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-8" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-8" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-8"></a><span class="w">      </span><span class="kt">var</span><span class="w"> </span><span class="n">isCurrent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">branchLine</span><span class="p">.</span><span class="n">StartsWith</span><span class="p">(</span><span class="sc">&#39;*&#39;</span><span class="p">);</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-9" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-9" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-9"></a><span class="w">      </span><span class="kt">var</span><span class="w"> </span><span class="n">branch</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">branchLine</span><span class="p">[</span><span class="mi">2</span><span class="p">..];</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-10" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-10" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-10"></a><span class="w">      </span><span class="kt">var</span><span class="w"> </span><span class="n">isRemote</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">false</span><span class="p">;</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-11" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-11" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-11"></a>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-12" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-12" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-12"></a><span class="w">      </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">branch</span><span class="p">.</span><span class="n">StartsWith</span><span class="p">(</span><span class="n">remotePrefix</span><span class="p">))</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-13" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-13" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-13"></a><span class="w">      </span><span class="p">{</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-14" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-14" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-14"></a><span class="w">        </span><span class="n">isRemote</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">true</span><span class="p">;</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-15" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-15" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-15"></a><span class="w">        </span><span class="n">branch</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">string</span><span class="p">.</span><span class="n">Join</span><span class="p">(</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-16" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-16" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-16"></a><span class="w">          </span><span class="sc">&#39;/&#39;</span><span class="p">,</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-17" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-17" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-17"></a><span class="w">          </span><span class="n">branch</span><span class="p">.</span><span class="n">Split</span><span class="p">(</span><span class="s">&quot; &quot;</span><span class="p">).</span><span class="n">First</span><span class="p">().</span><span class="n">Split</span><span class="p">(</span><span class="s">&quot;/&quot;</span><span class="p">).</span><span class="n">Skip</span><span class="p">(</span><span class="mi">2</span><span class="p">));</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-18" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-18" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-18"></a><span class="w">      </span><span class="p">}</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-19" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-19" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-19"></a>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-20" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-20" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-20"></a><span class="w">      </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="nf">Branch</span><span class="p">(</span><span class="n">branch</span><span class="p">,</span><span class="w"> </span><span class="n">isRemote</span><span class="p">,</span><span class="w"> </span><span class="n">isCurrent</span><span class="p">);</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-21" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-21" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-21"></a><span class="w">    </span><span class="p">})</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-22" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-22" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-22"></a><span class="w">    </span><span class="p">.</span><span class="n">OrderBy</span><span class="p">(</span><span class="n">b</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">b</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-23" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-23" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-23"></a><span class="w">    </span><span class="p">.</span><span class="n">ThenBy</span><span class="p">(</span><span class="n">b</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">b</span><span class="p">.</span><span class="n">IsRemote</span><span class="p">)</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-24" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-24" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-24"></a><span class="w">    </span><span class="p">.</span><span class="n">DistinctBy</span><span class="p">(</span><span class="n">b</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">b</span><span class="p">.</span><span class="n">Name</span><span class="p">)</span>
<a id="rest_code_c308c7ebd9924d0eb94e892197c52c65-25" name="rest_code_c308c7ebd9924d0eb94e892197c52c65-25" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_c308c7ebd9924d0eb94e892197c52c65-25"></a><span class="w">    </span><span class="p">.</span><span class="n">ToList</span><span class="p">();</span>
</pre></div>
<p>This snippet defines a fairly standard pipeline that goes from <code class="docutils literal">git</code> output (a single string) to a list of parsed objects. This pipeline is a function (or a static method, to be more precise). This function uses expression-bodied members: since we can fit the entire pipeline in a single expression, we can skip the braces and the <code class="docutils literal">return</code> keyword, and instead use a more compact syntax with an arrow (<code class="docutils literal">=&gt;</code>). After some cleanups and sanitization of the string, we split the string by the <code class="docutils literal">\n</code> character, and the type of our pipeline changes from <code class="docutils literal">string</code> to <code class="docutils literal">string[]</code> (an array of strings). We then use five operations from the <code class="docutils literal">System.Linq</code> namespace. Those operations are extension methods for enumerables (<code class="docutils literal">IEnumerable&lt;T&gt;</code>) — adding <code class="docutils literal">using System.Linq;</code> at the top of your program adds those methods to any enumerables (including arrays, lists, dictionaries, sets…).</p>
<p>The first operation is a <code class="docutils literal">Select</code>. LINQ methods are inspired by SQL; the more typical name for this one would be <code class="docutils literal">map</code>. (Similarly, <code class="docutils literal">Where</code> is LINQ’s name for <code class="docutils literal">filter</code>.) The logic inside <code class="docutils literal">Select</code> is written in a multi-line anonymous function (lambda), with braces (so there’s a <code class="docutils literal">return</code>) <a class="brackets" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#footnote-2" id="footnote-reference-2" role="doc-noteref"><span class="fn-bracket">[</span>2<span class="fn-bracket">]</span></a>. Inside that anonymous function, there are a few niceties, such as <code class="docutils literal">var</code> (type inference for variables), slicing (<code class="docutils literal"><span class="pre">[2..]</span></code>), as well as some more LINQ in string manipulations (<code class="docutils literal">.First()</code> and <code class="docutils literal">.Skip(2)</code>, which do what they say on the tin).</p>
<p>The next three operations are fairly straightforward sorting, and extracting unique values. Those use single-expression lambdas, which don’t use <code class="docutils literal">return</code>. The pipeline ends with converting <code class="docutils literal">IEnumerable&lt;Branch&gt;</code> (which appeared at the <code class="docutils literal">.Select()</code> stage) into a <code class="docutils literal">List&lt;Branch&gt;</code>.</p>
</section>
<section id="snippet-2">
<h2>Snippet 2</h2>
<div class="code"><pre class="code csharp"><a id="rest_code_390cb327a66a4edcb2286cae95421dbc-1" name="rest_code_390cb327a66a4edcb2286cae95421dbc-1" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-1"></a><span class="k">public</span><span class="w"> </span><span class="k">static</span><span class="w"> </span><span class="n">IEnumerable</span><span class="o">&lt;</span><span class="n">BranchDisplay</span><span class="o">&gt;</span><span class="w"> </span><span class="n">FilterAndNumberBranches</span><span class="p">(</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-2" name="rest_code_390cb327a66a4edcb2286cae95421dbc-2" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-2"></a><span class="w"> </span><span class="n">List</span><span class="o">&lt;</span><span class="n">Branch</span><span class="o">&gt;</span><span class="w"> </span><span class="n">branches</span><span class="p">,</span><span class="w"> </span><span class="kt">string?</span><span class="w"> </span><span class="n">filter</span><span class="p">)</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-3" name="rest_code_390cb327a66a4edcb2286cae95421dbc-3" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-3"></a><span class="p">{</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-4" name="rest_code_390cb327a66a4edcb2286cae95421dbc-4" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-4"></a><span class="w">  </span><span class="kt">var</span><span class="w"> </span><span class="n">branchWidth</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">branches</span><span class="p">.</span><span class="n">Count</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-5" name="rest_code_390cb327a66a4edcb2286cae95421dbc-5" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-5"></a><span class="w">    </span><span class="p">.</span><span class="n">ToString</span><span class="p">(</span><span class="n">CultureInfo</span><span class="p">.</span><span class="n">InvariantCulture</span><span class="p">).</span><span class="n">Length</span><span class="p">;</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-6" name="rest_code_390cb327a66a4edcb2286cae95421dbc-6" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-6"></a><span class="w">  </span><span class="kt">var</span><span class="w"> </span><span class="n">numberFormatString</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">$&quot;{{0,{branchWidth}}}. &quot;</span><span class="p">;</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-7" name="rest_code_390cb327a66a4edcb2286cae95421dbc-7" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-7"></a>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-8" name="rest_code_390cb327a66a4edcb2286cae95421dbc-8" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-8"></a><span class="w">  </span><span class="k">return</span><span class="w"> </span><span class="n">branches</span><span class="p">.</span><span class="n">Select</span><span class="p">(</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-9" name="rest_code_390cb327a66a4edcb2286cae95421dbc-9" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-9"></a><span class="w">    </span><span class="p">(</span><span class="n">branch</span><span class="p">,</span><span class="w"> </span><span class="n">index</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-10" name="rest_code_390cb327a66a4edcb2286cae95421dbc-10" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-10"></a><span class="w">      </span><span class="k">new</span><span class="w"> </span><span class="nf">BranchDisplay</span><span class="p">(</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-11" name="rest_code_390cb327a66a4edcb2286cae95421dbc-11" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-11"></a><span class="w">        </span><span class="n">Number</span><span class="p">:</span><span class="w"> </span><span class="kt">string</span><span class="p">.</span><span class="n">Format</span><span class="p">(</span><span class="n">numberFormatString</span><span class="p">,</span><span class="w"> </span><span class="n">index</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">),</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-12" name="rest_code_390cb327a66a4edcb2286cae95421dbc-12" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-12"></a><span class="w">        </span><span class="n">BranchName</span><span class="p">:</span><span class="w"> </span><span class="n">branch</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-13" name="rest_code_390cb327a66a4edcb2286cae95421dbc-13" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-13"></a><span class="w">        </span><span class="n">IsRemote</span><span class="p">:</span><span class="w"> </span><span class="n">branch</span><span class="p">.</span><span class="n">IsRemote</span><span class="p">,</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-14" name="rest_code_390cb327a66a4edcb2286cae95421dbc-14" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-14"></a><span class="w">        </span><span class="n">IsCurrent</span><span class="p">:</span><span class="w"> </span><span class="n">branch</span><span class="p">.</span><span class="n">IsCurrent</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-15" name="rest_code_390cb327a66a4edcb2286cae95421dbc-15" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-15"></a><span class="w">      </span><span class="p">)</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-16" name="rest_code_390cb327a66a4edcb2286cae95421dbc-16" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-16"></a><span class="w">    </span><span class="p">).</span><span class="n">Where</span><span class="p">(</span><span class="n">branchDisplay</span><span class="w"> </span><span class="o">=&gt;</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-17" name="rest_code_390cb327a66a4edcb2286cae95421dbc-17" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-17"></a><span class="w">      </span><span class="n">filter</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="k">null</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">branchDisplay</span><span class="p">.</span><span class="n">BranchName</span><span class="p">.</span><span class="n">Contains</span><span class="p">(</span><span class="n">filter</span><span class="p">));</span>
<a id="rest_code_390cb327a66a4edcb2286cae95421dbc-18" name="rest_code_390cb327a66a4edcb2286cae95421dbc-18" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_390cb327a66a4edcb2286cae95421dbc-18"></a><span class="p">}</span>
</pre></div>
<p>This function adds numbers to the branch list, and then filters branches based on the user’s query. The first thing to notice is the second argument: <code class="docutils literal">string? filter</code>. C# has support for nullable types, which means the compiler warns you if you use a possibly null value somewhere it isn’t expected <a class="brackets" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#footnote-3" id="footnote-reference-3" role="doc-noteref"><span class="fn-bracket">[</span>3<span class="fn-bracket">]</span></a>. <code class="docutils literal">numberFormatString</code> uses an interpolated string, in which <code class="docutils literal">{branchWidth}</code> will be replaced with the variable defined before. In the LINQ expression, you can see two interesting things: one is a two-argument lambda for <code class="docutils literal">Select</code>, and argument names, which can be optionally passed to functions and constructors for readability or to set parameters out of order.</p>
</section>
<section id="snippet-3">
<h2>Snippet 3</h2>
<p>How much boilerplate do you need to define an immutable data class with a constructor, value equality, and a string representation?</p>
<p>Exactly zero:</p>
<div class="code"><pre class="code csharp"><a id="rest_code_f4e830a38a2b43829677894c7c4094cd-1" name="rest_code_f4e830a38a2b43829677894c7c4094cd-1" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_f4e830a38a2b43829677894c7c4094cd-1"></a><span class="k">public</span><span class="w"> </span><span class="k">record</span><span class="w"> </span><span class="nf">Branch</span><span class="p">(</span>
<a id="rest_code_f4e830a38a2b43829677894c7c4094cd-2" name="rest_code_f4e830a38a2b43829677894c7c4094cd-2" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_f4e830a38a2b43829677894c7c4094cd-2"></a><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="n">Name</span><span class="p">,</span>
<a id="rest_code_f4e830a38a2b43829677894c7c4094cd-3" name="rest_code_f4e830a38a2b43829677894c7c4094cd-3" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_f4e830a38a2b43829677894c7c4094cd-3"></a><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="n">IsRemote</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">false</span><span class="p">,</span>
<a id="rest_code_f4e830a38a2b43829677894c7c4094cd-4" name="rest_code_f4e830a38a2b43829677894c7c4094cd-4" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_f4e830a38a2b43829677894c7c4094cd-4"></a><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="n">IsCurrent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">false</span><span class="p">);</span>
<a id="rest_code_f4e830a38a2b43829677894c7c4094cd-5" name="rest_code_f4e830a38a2b43829677894c7c4094cd-5" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_f4e830a38a2b43829677894c7c4094cd-5"></a>
<a id="rest_code_f4e830a38a2b43829677894c7c4094cd-6" name="rest_code_f4e830a38a2b43829677894c7c4094cd-6" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_f4e830a38a2b43829677894c7c4094cd-6"></a><span class="k">public</span><span class="w"> </span><span class="k">record</span><span class="w"> </span><span class="nf">BranchDisplay</span><span class="p">(</span>
<a id="rest_code_f4e830a38a2b43829677894c7c4094cd-7" name="rest_code_f4e830a38a2b43829677894c7c4094cd-7" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_f4e830a38a2b43829677894c7c4094cd-7"></a><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="n">Number</span><span class="p">,</span>
<a id="rest_code_f4e830a38a2b43829677894c7c4094cd-8" name="rest_code_f4e830a38a2b43829677894c7c4094cd-8" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_f4e830a38a2b43829677894c7c4094cd-8"></a><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="n">BranchName</span><span class="p">,</span>
<a id="rest_code_f4e830a38a2b43829677894c7c4094cd-9" name="rest_code_f4e830a38a2b43829677894c7c4094cd-9" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_f4e830a38a2b43829677894c7c4094cd-9"></a><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="n">IsRemote</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">false</span><span class="p">,</span>
<a id="rest_code_f4e830a38a2b43829677894c7c4094cd-10" name="rest_code_f4e830a38a2b43829677894c7c4094cd-10" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_f4e830a38a2b43829677894c7c4094cd-10"></a><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="n">IsCurrent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">false</span><span class="p">);</span>
</pre></div>
<p>(If you want things to be mutable, you do need to write some more code. Still, all you need for encapsulated properties is <code class="docutils literal">int Foo { get; set; }</code>, which is miles better than having to write out getters and setters by hand, as you would do in Java.)</p>
</section>
</section>
<section id="dependency-management">
<h1>Dependency management</h1>
<p>Gitco.NET is a fairly simple thing, and it doesn’t need any third-party libraries, it can do its job with just the standard library.</p>
<p>However, Gitco.NET has a test suite. .NET doesn’t ship with a unit testing framework. There are three popular options, I picked xUnit (which is the most popular). I created the test project with a template, and then added a reference to the main code (under test). I ended up with the following project file (<code class="docutils literal">gitco.NET.Tests.csproj</code>):</p>
<div class="code"><pre class="code xml"><a id="rest_code_3c2ae6d4077e4f6380698e2423682114-1" name="rest_code_3c2ae6d4077e4f6380698e2423682114-1" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-1"></a><span class="nt">&lt;Project</span><span class="w"> </span><span class="na">Sdk=</span><span class="s">&quot;Microsoft.NET.Sdk&quot;</span><span class="nt">&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-2" name="rest_code_3c2ae6d4077e4f6380698e2423682114-2" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-2"></a>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-3" name="rest_code_3c2ae6d4077e4f6380698e2423682114-3" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-3"></a><span class="w">  </span><span class="nt">&lt;PropertyGroup&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-4" name="rest_code_3c2ae6d4077e4f6380698e2423682114-4" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-4"></a><span class="w">    </span><span class="nt">&lt;TargetFramework&gt;</span>net6.0<span class="nt">&lt;/TargetFramework&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-5" name="rest_code_3c2ae6d4077e4f6380698e2423682114-5" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-5"></a><span class="w">    </span><span class="nt">&lt;ImplicitUsings&gt;</span>enable<span class="nt">&lt;/ImplicitUsings&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-6" name="rest_code_3c2ae6d4077e4f6380698e2423682114-6" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-6"></a><span class="w">    </span><span class="nt">&lt;LangVersion&gt;</span>10.0<span class="nt">&lt;/LangVersion&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-7" name="rest_code_3c2ae6d4077e4f6380698e2423682114-7" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-7"></a><span class="w">    </span><span class="nt">&lt;Nullable&gt;</span>enable<span class="nt">&lt;/Nullable&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-8" name="rest_code_3c2ae6d4077e4f6380698e2423682114-8" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-8"></a><span class="w">    </span><span class="nt">&lt;IsPackable&gt;</span>false<span class="nt">&lt;/IsPackable&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-9" name="rest_code_3c2ae6d4077e4f6380698e2423682114-9" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-9"></a><span class="w">  </span><span class="nt">&lt;/PropertyGroup&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-10" name="rest_code_3c2ae6d4077e4f6380698e2423682114-10" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-10"></a>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-11" name="rest_code_3c2ae6d4077e4f6380698e2423682114-11" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-11"></a><span class="w">  </span><span class="nt">&lt;ItemGroup&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-12" name="rest_code_3c2ae6d4077e4f6380698e2423682114-12" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-12"></a><span class="w">    </span><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;Microsoft.NET.Test.Sdk&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;17.1.0&quot;</span><span class="w"> </span><span class="nt">/&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-13" name="rest_code_3c2ae6d4077e4f6380698e2423682114-13" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-13"></a><span class="w">    </span><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;xunit&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;2.4.1&quot;</span><span class="w"> </span><span class="nt">/&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-14" name="rest_code_3c2ae6d4077e4f6380698e2423682114-14" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-14"></a><span class="w">    </span><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;xunit.runner.visualstudio&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;2.4.3&quot;</span><span class="nt">&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-15" name="rest_code_3c2ae6d4077e4f6380698e2423682114-15" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-15"></a><span class="w">      </span><span class="nt">&lt;IncludeAssets&gt;</span>runtime;<span class="w"> </span>build;<span class="w"> </span>native;<span class="w"> </span>contentfiles;<span class="w"> </span>analyzers;<span class="w"> </span>buildtransitive<span class="nt">&lt;/IncludeAssets&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-16" name="rest_code_3c2ae6d4077e4f6380698e2423682114-16" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-16"></a><span class="w">      </span><span class="nt">&lt;PrivateAssets&gt;</span>all<span class="nt">&lt;/PrivateAssets&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-17" name="rest_code_3c2ae6d4077e4f6380698e2423682114-17" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-17"></a><span class="w">    </span><span class="nt">&lt;/PackageReference&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-18" name="rest_code_3c2ae6d4077e4f6380698e2423682114-18" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-18"></a><span class="w">    </span><span class="nt">&lt;PackageReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;coverlet.collector&quot;</span><span class="w"> </span><span class="na">Version=</span><span class="s">&quot;3.1.2&quot;</span><span class="nt">&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-19" name="rest_code_3c2ae6d4077e4f6380698e2423682114-19" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-19"></a><span class="w">      </span><span class="nt">&lt;IncludeAssets&gt;</span>runtime;<span class="w"> </span>build;<span class="w"> </span>native;<span class="w"> </span>contentfiles;<span class="w"> </span>analyzers;<span class="w"> </span>buildtransitive<span class="nt">&lt;/IncludeAssets&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-20" name="rest_code_3c2ae6d4077e4f6380698e2423682114-20" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-20"></a><span class="w">      </span><span class="nt">&lt;PrivateAssets&gt;</span>all<span class="nt">&lt;/PrivateAssets&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-21" name="rest_code_3c2ae6d4077e4f6380698e2423682114-21" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-21"></a><span class="w">    </span><span class="nt">&lt;/PackageReference&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-22" name="rest_code_3c2ae6d4077e4f6380698e2423682114-22" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-22"></a><span class="w">  </span><span class="nt">&lt;/ItemGroup&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-23" name="rest_code_3c2ae6d4077e4f6380698e2423682114-23" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-23"></a>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-24" name="rest_code_3c2ae6d4077e4f6380698e2423682114-24" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-24"></a><span class="w">  </span><span class="nt">&lt;ItemGroup&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-25" name="rest_code_3c2ae6d4077e4f6380698e2423682114-25" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-25"></a><span class="w">    </span><span class="nt">&lt;ProjectReference</span><span class="w"> </span><span class="na">Include=</span><span class="s">&quot;..\gitco.NET\gitco.NET.csproj&quot;</span><span class="w"> </span><span class="nt">/&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-26" name="rest_code_3c2ae6d4077e4f6380698e2423682114-26" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-26"></a><span class="w">  </span><span class="nt">&lt;/ItemGroup&gt;</span>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-27" name="rest_code_3c2ae6d4077e4f6380698e2423682114-27" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-27"></a>
<a id="rest_code_3c2ae6d4077e4f6380698e2423682114-28" name="rest_code_3c2ae6d4077e4f6380698e2423682114-28" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#rest_code_3c2ae6d4077e4f6380698e2423682114-28"></a><span class="nt">&lt;/Project&gt;</span>
</pre></div>
<p>Yeah, it’s an XML file. But it’s pretty straightforward: there’s a <code class="docutils literal">&lt;PropertyGroup&gt;</code> with some project configuration, and two <code class="docutils literal">&lt;ItemGroup&gt;&gt;</code> tags. One of them has <code class="docutils literal">&lt;PackageReference&gt;</code> tags, which specify third-party dependencies to use. The other has a <code class="docutils literal">&lt;ProjectReference&gt;</code> to the main code, pointing at its <code class="docutils literal">.csproj</code> file. (Note that this split is arbitrary, you can have as many <code class="docutils literal">&lt;ItemGroup&gt;</code> tags as you want, you could have just one with both package and project references.)</p>
<p>How does this work? Quite simply, and transparently to the developer. Building the project will lead to packages being <em>restored</em> (fetched from NuGet, or copied from the local NuGet cache). There are no “virtual environments” to manage, there aren’t 10 competing package managers. Visual Studio will also expect both projects to be part of one solution, which is something you’d likely do anyway for convenient access to both at the same time.</p>
</section>
<section id="tooling">
<h1>Tooling</h1>
<section id="dotnet-cli">
<h2>dotnet CLI</h2>
<p>.NET has a CLI for performing typical build and project configuration tasks. You can <code class="docutils literal">dotnet build</code> a project, you can <code class="docutils literal">dotnet run</code> it, you can <code class="docutils literal">dotnet test</code> things, and you can <code class="docutils literal">dotnet publish</code>. The CLI figures out what to do, it restores the dependencies if needed, it handles the compilation of your code. If you type <code class="docutils literal">dotnet test</code> in a directory with your solution file (<code class="docutils literal">.sln</code>), it will restore dependencies, build the code, and then find tests and run them.</p>
</section>
<section id="ide">
<h2>IDE</h2>
<p>What IDE should you use? There are a few options:</p>
<ul class="simple">
<li><p><strong>Visual Studio Code.</strong> The quite advanced text editor supports pretty much any language. C# support works okay, with all the IDE features available, but in my experience, it can sometimes get confused (requiring a restart of the IDE). You will probably need to spend some more time with the <code class="docutils literal">dotnet</code> CLI than you would with the other options.</p></li>
<li><p><strong>Visual Studio for Windows.</strong> The IDE with the purple icon is an option, although VS can feel arcane to people used to other IDEs/editors, and the Vim bindings are quite bad (especially if you select things with a mouse sometimes). It’s free for personal and very-small-business use, but for anything even slightly serious, you’ll need paid licenses.</p></li>
<li><p><strong>Visual Studio for Mac.</strong> A completely separate product, works reasonably well, same pricing as with the Windows version.</p></li>
<li><p><strong>Visual Studio for Windows + ReSharper Ultimate.</strong> Adding this (paid) extension makes VS much smarter, although it can also affect performance negatively.</p></li>
<li><p><strong>JetBrains Rider.</strong> This is an IDE based on the IntelliJ platform, with all the magic seen in ReSharper (as well as other JetBrains IDEs), but none of the performance issues and Visual Studio being Visual Studio (although if you do prefer VS behaviors and keyboard shortcuts, you can configure those as well). This is probably your best bet if you’re willing to invest some money (or your employer is).</p></li>
</ul>
</section>
</section>
<section id="why-should-i-pick-it-over-x">
<h1>Why should I pick it over X?</h1>
<p>Well, it depends. If this post has piqued your interest, perhaps install the SDK and write some small things to get a feel for the language and to see if it’s for you. (And note this post didn’t cover the Web stuff.)</p>
<p>But here are a few things of note:</p>
<section id="python">
<h2>Python</h2>
<ul class="simple">
<li><p>C# is statically typed. Modern Python’s static typing (via things like mypy) is quite cool, but not all libraries and ecosystems have adopted it. Statically typed languages are safer, and allow IDEs to be smarter.</p></li>
<li><p>C# has a better approach to functional programming. Python has ugly and single-expression lambdas (with a pointless <code class="docutils literal">lambda</code> keyword), C# has inline functions that can contain multiple statements.</p></li>
<li><p>C# has much better package management.</p></li>
<li><p>C# is trivial to compile to a single-file executable.</p></li>
<li><p>C# is much faster than Python.</p></li>
<li><p>~Nobody does machine learning and data science in C#, which is a plus in my book.</p></li>
</ul>
</section>
<section id="java">
<h2>Java</h2>
<ul class="simple">
<li><p>C# has a lot more developer-quality-of-life features and less boilerplate. For example, Lists and Dictionaries can be accessed using brackets, and properties are accessible via dot notation instead of having to explicitly call getter and setter methods.</p></li>
<li><p>C#’s generic are more flexible, as they aren’t erased on compilation.</p></li>
<li><p>C# has null safety. It also has the safe navigation <code class="docutils literal"><span class="pre">?.</span></code> operator, and the null coalescing <code class="docutils literal"><span class="pre">??</span></code> operator, both of which make working with nullable values easier.</p></li>
<li><p>C# has easy concurrency via <code class="docutils literal">async</code> and <code class="docutils literal">await</code>.</p></li>
<li><p>Web stuff: Spring is painful, Spring Boot doesn’t make it much better. ASP.NET Core is much nicer.</p></li>
</ul>
<p><em>Additional reading:</em> Wikipedia has a very nice and detailed <a class="reference external" href="https://en.wikipedia.org/wiki/Comparison_of_C_Sharp_and_Java">Comparison of C# and Java</a>.</p>
</section>
<section id="also">
<h2>Also…</h2>
<ul class="simple">
<li><p>C# is a high-level language with automated memory management, which is very convenient in many use-cases.</p></li>
<li><p>C# has exceptions.</p></li>
<li><p>There are quite a lot of jobs for C# developers, although not necessarily in Silicon Valley.</p></li>
</ul>
</section>
<section id="but-on-the-other-hand">
<h2>But on the other hand…</h2>
<ul class="simple">
<li><p>C# can still sometimes feel a bit Windows-oriented.</p></li>
<li><p>C# jobs tend to be enterprisey.</p></li>
<li><p>Python is a great language to learn as a beginner. It’s also great for one-off things, interactive work, and scripting.</p></li>
<li><p>The non-Windows desktop GUI story isn’t too great, although it is getting better with MAUI (which supports macOS).</p></li>
<li><p>If you’re targeting mobile, I would probably focus on the native APIs and languages for the best user experience (Swift and Cocoa Touch for iOS; Kotlin and the Android Platform APIs for Android). That said, MAUI might be worth a go as well.</p></li>
<li><p>If you’re doing very low-level stuff, C# probably won’t cut it.</p></li>
<li><p>If you want real functional programming, go with F#. You might also prefer Scala or Haskell or such.</p></li>
<li><p>And if you’re making web front-end stuff, TypeScript (or plain JavaScript) is still your best bet. C# has Blazor, but I’d prefer for web apps not to embed all of .NET via WebAssembly.</p></li>
</ul>
<p>But for console apps, Windows desktop, and web back-end services? <strong>Do give C# a try,</strong> it might just win you over. It is a pretty good language, but one that was held back by the Windows association for a long time. But now it’s part of a modern, multi-platform, developer-friendly ecosystem.</p>
</section>
</section>
<section id="footnotes">
<h1>Footnotes</h1>
<aside class="footnote-list brackets">
<aside class="footnote brackets" id="footnote-1" role="doc-footnote">
<span class="label"><span class="fn-bracket">[</span><a role="doc-backlink" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#footnote-reference-1">1</a><span class="fn-bracket">]</span></span>
<p>The code samples in this post are using the usual Microsoft code style with braces on separate lines, the usual Microsoft naming convention (PascalCase for ~everything, camelCase for local variable names), and 2-space indentation, which isn’t the usual Microsoft style.</p>
</aside>
<aside class="footnote brackets" id="footnote-2" role="doc-footnote">
<span class="label"><span class="fn-bracket">[</span><a role="doc-backlink" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#footnote-reference-2">2</a><span class="fn-bracket">]</span></span>
<p>This could be moved to a separate static method. If that method was <code class="docutils literal">private static Branch ParseLineAsBranch(string branchLine)</code>, then the expression could be <code class="docutils literal">.Select(ParseLineAsBranch)</code>.</p>
</aside>
<aside class="footnote brackets" id="footnote-3" role="doc-footnote">
<span class="label"><span class="fn-bracket">[</span><a role="doc-backlink" href="https://chriswarrick.com/blog/2022/09/19/writing-a-console-app-in-c-sharp-for-fun-and-profit/#footnote-reference-3">3</a><span class="fn-bracket">]</span></span>
<p>There’s some inconsistency and mixing when working with nullables: nullable objects (such as <code class="docutils literal">string?</code>) are accessible directly, whereas nullable value types (such as <code class="docutils literal">int?</code>) need to be accessed with <code class="docutils literal">.Value</code>, due to historical reasons and implementation details.</p>
</aside>
</aside>
</section>
]]></content:encoded><category>C#/.NET</category><category>.NET</category><category>C#</category><category>programming</category><category>web development</category></item><item><title>Code writing code: Python and Vim as development aids</title><dc:creator>Chris Warrick</dc:creator><link>https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/</link><pubDate>Fri, 27 May 2016 08:46:35 GMT</pubDate><guid>https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/</guid><description>
Recently I was working on some C# and Java code. And along the way, I used
Python and Vim to (re)write my code. A small Python script and a 6-keystroke
Vim macro did it faster and better than a human would.
Every programmer should learn a good scripting language and use a programmable
editor like Vim. Why? Here are two examples, after the break.
</description><content:encoded><![CDATA[
<p>Recently I was working on some C# and Java code. And along the way, I used
Python and Vim to (re)write my code. A small Python script and a 6-keystroke
Vim macro did it faster and better than a human would.</p>
<p>Every programmer should learn a good scripting language and use a programmable
editor like Vim. Why? Here are two examples, after the break.</p>



<section id="episode-i-inotifypropertychanged-or-python-writing-c">
<h1>Episode I: <code class="docutils literal">INotifyPropertyChanged</code>, or Python writing C#</h1>
<p>I was building a private C# weekend project (that turned into a weeklong
project) — and by the way, WPF and C# are quite pleasant (Windows Forms is a
trainwreck, though). One of the things I used in that project was a DataGrid
bound to a list of custom objects (a DataGrid is a table, basically). And in
order to use it, you need to use the <code class="docutils literal">INotifyPropertyChanged</code> interface <a class="reference external" href="https://msdn.microsoft.com/en-us/library/ms229614(v=vs.100).aspx">(MSDN)</a>.
It involves doing something like this:</p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-1"><code data-line-number=" 1"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-1" name="rest_code_4c40930703c34f739bfa440b60bb2d79-1"></a><span class="w"> </span><span class="k">private</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="n">name_</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">get</span><span class="p">;</span><span class="w"> </span><span class="k">set</span><span class="p">;</span><span class="w"> </span><span class="p">};</span><span class="w"> </span><span class="c1">// can also be a field</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-2"><code data-line-number=" 2"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-2" name="rest_code_4c40930703c34f739bfa440b60bb2d79-2"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-3"><code data-line-number=" 3"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-3" name="rest_code_4c40930703c34f739bfa440b60bb2d79-3"></a><span class="w"> </span><span class="na">[JsonProperty]</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-4"><code data-line-number=" 4"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-4" name="rest_code_4c40930703c34f739bfa440b60bb2d79-4"></a><span class="w"> </span><span class="k">public</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-5"><code data-line-number=" 5"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-5" name="rest_code_4c40930703c34f739bfa440b60bb2d79-5"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="k">get</span><span class="w"> </span><span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-6"><code data-line-number=" 6"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-6" name="rest_code_4c40930703c34f739bfa440b60bb2d79-6"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="k">return</span><span class="w"> </span><span class="n">name_</span><span class="p">;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-7"><code data-line-number=" 7"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-7" name="rest_code_4c40930703c34f739bfa440b60bb2d79-7"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-8"><code data-line-number=" 8"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-8" name="rest_code_4c40930703c34f739bfa440b60bb2d79-8"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="k">set</span><span class="w"> </span><span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-9"><code data-line-number=" 9"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-9" name="rest_code_4c40930703c34f739bfa440b60bb2d79-9"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="k">value</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="n">name_</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-10"><code data-line-number="10"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-10" name="rest_code_4c40930703c34f739bfa440b60bb2d79-10"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="n">name_</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">value</span><span class="p">;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-11"><code data-line-number="11"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-11" name="rest_code_4c40930703c34f739bfa440b60bb2d79-11"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="n">NotifyPropertyChanged</span><span class="p">(</span><span class="s">&quot;name&quot;</span><span class="p">);</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-12"><code data-line-number="12"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-12" name="rest_code_4c40930703c34f739bfa440b60bb2d79-12"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-13"><code data-line-number="13"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-13" name="rest_code_4c40930703c34f739bfa440b60bb2d79-13"></a><span class="w">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="p">}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4c40930703c34f739bfa440b60bb2d79-14"><code data-line-number="14"></code></a></td><td class="code"><code><a id="rest_code_4c40930703c34f739bfa440b60bb2d79-14" name="rest_code_4c40930703c34f739bfa440b60bb2d79-14"></a><span class="w"> </span><span class="p">}</span>
</code></td></tr></table></div><p>That’s 12 lines of code (excluding <code class="docutils literal">[JsonProperty]</code> which comes from the
Json.NET library) for that pattern. Oh: and I need to do that for <strong>every</strong>
field/property of my class, because otherwise any changes to them would not be
reflected in the tables (and maybe one or two fields were <em>not</em> in the table).</p>
<p>Doing that by hand is really not feasible: you need to copy-paste this
large block 14 times and take care of 5 instances of the name (3 with
underscores, 2 without), 2 instances of the type, and the <code class="docutils literal">[JsonProperty]</code>
attribute (which does not appear on all properties).</p>
<p>So, I used one of those intelligent computer things to do it for me. I wrote a
really simple <a class="reference external" href="https://www.python.org/">Python</a> script and ran it. And I ended up with all 14 fields built
for me.</p>
<p><a class="reference external" href="link://listing/listings/code-writing-code/write_properties.py">code-writing-code/write_properties.py</a>  <a class="reference external" href="link://listing_source/listings/code-writing-code/write_properties.py">(Source)</a></p>
<div class="code"><table class="codetable"><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-1"><code data-line-number=" 1"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-1" name="rest_code_5defa6b60bf345e8ac072f136362524b-1"></a><span class="ch">#!/usr/bin/env python3</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-2"><code data-line-number=" 2"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-2" name="rest_code_5defa6b60bf345e8ac072f136362524b-2"></a><span class="n">TEMPLATE</span> <span class="o">=</span> <span class="s2">&quot;&quot;&quot;</span><span class="se">\</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-3"><code data-line-number=" 3"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-3" name="rest_code_5defa6b60bf345e8ac072f136362524b-3"></a><span class="s2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="si">%s</span><span class="s2">public </span><span class="si">%s</span><span class="s2"> </span><span class="si">%s</span><span class="s2"> {</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-4"><code data-line-number=" 4"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-4" name="rest_code_5defa6b60bf345e8ac072f136362524b-4"></a><span class="s2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;get {</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-5"><code data-line-number=" 5"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-5" name="rest_code_5defa6b60bf345e8ac072f136362524b-5"></a><span class="s2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return </span><span class="si">%s</span><span class="s2">_;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-6"><code data-line-number=" 6"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-6" name="rest_code_5defa6b60bf345e8ac072f136362524b-6"></a><span class="s2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-7"><code data-line-number=" 7"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-7" name="rest_code_5defa6b60bf345e8ac072f136362524b-7"></a><span class="s2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set {</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-8"><code data-line-number=" 8"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-8" name="rest_code_5defa6b60bf345e8ac072f136362524b-8"></a><span class="s2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (value != </span><span class="si">%s</span><span class="s2">_) {</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-9"><code data-line-number=" 9"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-9" name="rest_code_5defa6b60bf345e8ac072f136362524b-9"></a><span class="s2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="si">%s</span><span class="s2">_ = value;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-10"><code data-line-number="10"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-10" name="rest_code_5defa6b60bf345e8ac072f136362524b-10"></a><span class="s2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NotifyPropertyChanged(&quot;</span><span class="si">%s</span><span class="s2">&quot;);</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-11"><code data-line-number="11"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-11" name="rest_code_5defa6b60bf345e8ac072f136362524b-11"></a><span class="s2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-12"><code data-line-number="12"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-12" name="rest_code_5defa6b60bf345e8ac072f136362524b-12"></a><span class="s2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-13"><code data-line-number="13"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-13" name="rest_code_5defa6b60bf345e8ac072f136362524b-13"></a><span class="s2">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-14"><code data-line-number="14"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-14" name="rest_code_5defa6b60bf345e8ac072f136362524b-14"></a><span class="s2">&quot;&quot;&quot;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-15"><code data-line-number="15"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-15" name="rest_code_5defa6b60bf345e8ac072f136362524b-15"></a><span class="n">JSONPROPERTY_TEMPLATE</span> <span class="o">=</span> <span class="s1">&#39;[JsonProperty]</span><span class="se">\n</span><span class="s1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#39;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-16"><code data-line-number="16"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-16" name="rest_code_5defa6b60bf345e8ac072f136362524b-16"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-17"><code data-line-number="17"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-17" name="rest_code_5defa6b60bf345e8ac072f136362524b-17"></a><span class="k">def</span><span class="w"> </span><span class="nf">write</span><span class="p">(</span><span class="n">has_jsonproperty</span><span class="p">,</span> <span class="n">vtype</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-18"><code data-line-number="18"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-18" name="rest_code_5defa6b60bf345e8ac072f136362524b-18"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">if</span> <span class="n">has_jsonproperty</span><span class="p">:</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-19"><code data-line-number="19"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-19" name="rest_code_5defa6b60bf345e8ac072f136362524b-19"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">jsonproperty</span> <span class="o">=</span> <span class="n">JSONPROPERTY_TEMPLATE</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-20"><code data-line-number="20"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-20" name="rest_code_5defa6b60bf345e8ac072f136362524b-20"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">else</span><span class="p">:</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-21"><code data-line-number="21"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-21" name="rest_code_5defa6b60bf345e8ac072f136362524b-21"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="n">jsonproperty</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-22"><code data-line-number="22"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-22" name="rest_code_5defa6b60bf345e8ac072f136362524b-22"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="k">return</span> <span class="n">TEMPLATE</span> <span class="o">%</span> <span class="p">(</span><span class="n">jsonproperty</span><span class="p">,</span> <span class="n">vtype</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-23"><code data-line-number="23"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-23" name="rest_code_5defa6b60bf345e8ac072f136362524b-23"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-24"><code data-line-number="24"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-24" name="rest_code_5defa6b60bf345e8ac072f136362524b-24"></a><span class="n">properties</span> <span class="o">=</span> <span class="p">[</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-25"><code data-line-number="25"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-25" name="rest_code_5defa6b60bf345e8ac072f136362524b-25"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="s1">&#39;1 string name&#39;</span><span class="p">,</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-26"><code data-line-number="26"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-26" name="rest_code_5defa6b60bf345e8ac072f136362524b-26"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="s1">&#39;0 int another&#39;</span><span class="p">,</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-27"><code data-line-number="27"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-27" name="rest_code_5defa6b60bf345e8ac072f136362524b-27"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="c1"># 12 fields omitted for brevity</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-28"><code data-line-number="28"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-28" name="rest_code_5defa6b60bf345e8ac072f136362524b-28"></a><span class="p">]</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-29"><code data-line-number="29"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-29" name="rest_code_5defa6b60bf345e8ac072f136362524b-29"></a><span class="n">properties_split</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span><span class="o">.</span><span class="n">split</span><span class="p">()</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">properties</span><span class="p">]</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-30"><code data-line-number="30"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-30" name="rest_code_5defa6b60bf345e8ac072f136362524b-30"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-31"><code data-line-number="31"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-31" name="rest_code_5defa6b60bf345e8ac072f136362524b-31"></a><span class="c1"># Private definitions (internal)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-32"><code data-line-number="32"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-32" name="rest_code_5defa6b60bf345e8ac072f136362524b-32"></a><span class="k">for</span> <span class="n">has_jsonproperty</span><span class="p">,</span> <span class="n">vtype</span><span class="p">,</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">properties_split</span><span class="p">:</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-33"><code data-line-number="33"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-33" name="rest_code_5defa6b60bf345e8ac072f136362524b-33"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;private </span><span class="si">%s</span><span class="s2"> </span><span class="si">%s</span><span class="s2">_ { get; set; }&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">vtype</span><span class="p">,</span> <span class="n">name</span><span class="p">))</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-34"><code data-line-number="34"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-34" name="rest_code_5defa6b60bf345e8ac072f136362524b-34"></a>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-35"><code data-line-number="35"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-35" name="rest_code_5defa6b60bf345e8ac072f136362524b-35"></a><span class="nb">print</span><span class="p">()</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-36"><code data-line-number="36"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-36" name="rest_code_5defa6b60bf345e8ac072f136362524b-36"></a><span class="c1"># Public definitions (with notifications)</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-37"><code data-line-number="37"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-37" name="rest_code_5defa6b60bf345e8ac072f136362524b-37"></a><span class="k">for</span> <span class="n">has_jsonproperty</span><span class="p">,</span> <span class="n">vtype</span><span class="p">,</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">properties_split</span><span class="p">:</span>
</code></td></tr><tr><td class="linenos linenodiv"><a href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_5defa6b60bf345e8ac072f136362524b-38"><code data-line-number="38"></code></a></td><td class="code"><code><a id="rest_code_5defa6b60bf345e8ac072f136362524b-38" name="rest_code_5defa6b60bf345e8ac072f136362524b-38"></a>&nbsp;&nbsp;&nbsp;&nbsp;<span class="nb">print</span><span class="p">(</span><span class="n">write</span><span class="p">(</span><span class="n">has_jsonproperty</span> <span class="o">==</span> <span class="s1">&#39;1&#39;</span><span class="p">,</span> <span class="n">vtype</span><span class="p">,</span> <span class="n">name</span><span class="p">))</span>
</code></td></tr></table></div><p>That script takes a list of properties and spits out a block of code, ready to
be pasted into the code. Visual Studio has a nice <em>Insert File as Text</em>
feature, so redirecting the output to a file and using that option is enough.</p>
</section>
<section id="episode-ii-fixing-argument-order-or-vim-re-writing-java">
<h1>Episode II: Fixing argument order, or Vim (re)writing Java</h1>
<p>Another project, <a class="reference external" href="https://github.com/Kwpolska/numbernamer">Number Namer</a>, written in Java, and it does what it says on
the tin: takes a number and writes it out as words, while being multilingual and
extensible. I used Eclipse for this project, because it looks good, is really
helpful with its code linting, and does not run slowly on my aging system (I’m
looking at you, IntelliJ IDEA aka PyCharm aka Android Studio…)</p>
<p>And so, I was building a test suite, using <a class="reference external" href="http://junit.org/">JUnit</a>. It’s pretty
straightforward, and I remember the syntax from Python’s unittest (even though
I write tests with <a class="reference external" href="http://pytest.org/">pytest</a> nowadays). Or so I thought.</p>
<div class="code"><pre class="code java"><a id="rest_code_4fd345f92b594e8bb7094164392eebcd-1" name="rest_code_4fd345f92b594e8bb7094164392eebcd-1" href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4fd345f92b594e8bb7094164392eebcd-1"></a><span class="c1">// (incorrect)</span>
<a id="rest_code_4fd345f92b594e8bb7094164392eebcd-2" name="rest_code_4fd345f92b594e8bb7094164392eebcd-2" href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4fd345f92b594e8bb7094164392eebcd-2"></a><span class="n">assertEquals</span><span class="p">(</span><span class="s">&quot;Basic integers (7) failed&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">namer</span><span class="p">.</span><span class="na">name</span><span class="p">(</span><span class="mi">7L</span><span class="p">),</span><span class="w"> </span><span class="s">&quot;seven&quot;</span><span class="p">);</span>
<a id="rest_code_4fd345f92b594e8bb7094164392eebcd-3" name="rest_code_4fd345f92b594e8bb7094164392eebcd-3" href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4fd345f92b594e8bb7094164392eebcd-3"></a><span class="c1">// (fixed)                              ^ cursor</span>
<a id="rest_code_4fd345f92b594e8bb7094164392eebcd-4" name="rest_code_4fd345f92b594e8bb7094164392eebcd-4" href="https://chriswarrick.com/blog/2016/05/27/code-writing-code-python-and-vim/#rest_code_4fd345f92b594e8bb7094164392eebcd-4"></a><span class="n">assertEquals</span><span class="p">(</span><span class="s">&quot;Basic integers (7) failed&quot;</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;seven&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">namer</span><span class="p">.</span><span class="na">name</span><span class="p">(</span><span class="mi">7L</span><span class="p">));</span>
</pre></div>
<p>You see, the typical Python spelling is <code class="docutils literal">self.assertEquals(actual,
expected)</code>. Java adds a <code class="docutils literal">String</code> message parameter and it also swaps
<code class="docutils literal">actual</code> and <code class="docutils literal">expected</code>. Which I didn’t notice at first, and I wrote my
assertions incorrectly. While it doesn’t <em>really</em> matter (it will still work),
the output looked a bit weird.</p>
<p>And I noticed only when I finished writing my tests (and I had a typo in my
expected output). I wanted to fix them all — not manually, of course. So, I
closed this file, brought up Vim, searched for the motion I need (it’s
<code class="docutils literal">t{char}</code> — see <code class="docutils literal">:help t</code>). And I ended up with this
(cursor placed on the comma after the first argument):</p>
<div style="text-align: center;">
<kbd style="font-size: 2em;">dt,</kbd><kbd style="font-size: 2em;">t)</kbd><kbd style="font-size: 2em;">p</kbd>
</div><p>What does this do, you may ask? It’s actually pretty self-explanatory:</p>
<blockquote><p>
<b>d</b>elete <b>t</b>ill comma, (go) <b>t</b>ill closing parenthesis, <b>p</b>aste.
</p></blockquote><p>This fixes one line. Automatically. Make it a macro (wrap in <code class="docutils literal">qq</code> … <code class="docutils literal">q</code>,
use with <code class="docutils literal">&#64;q</code>) and now you can run it on all lines, either by moving manually or by
searching for <code class="docutils literal">,</code> and pressing <code class="docutils literal">n&#64;q</code> until you run out of lines.</p>
</section>
<section id="epilogue">
<h1>Epilogue</h1>
<p>Some of you might say “but VS/Eclipse/IDEA has an option for that somewhere” or
“[expensive tool] can do that” — and a Google search shows that there is an
Eclipse plugin to swap arguments and that I could also write a regex to solve
my second issue. Nevertheless, Python is a great tool in a programmer’s toolbox
— especially the interactive interpreter. And Vim is an awesome editor that can
accomplish magic in a few keystrokes — and there are many more things you can
do with it.</p>
<p class="lead">Go learn <a class="reference external" href="https://www.python.org/">Python</a> and <a class="reference external" href="http://www.vim.org/">Vim</a> now.</p>
<p>Also: don’t even bother with VsVim or IdeaVim or any other Vim emulation
plugins, they work in unusual ways and often don’t give you everything — for
example, VsVim has a Vim visual mode (<code class="docutils literal">v</code> key) and Visual Studio selection
mode (mouse), and only one allows Vim keystrokes (the other will replace
selected text).</p>
</section>
]]></content:encoded><category>Programming</category><category>C#</category><category>Java</category><category>programming</category><category>Python</category><category>Vim</category></item></channel></rss>