<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Rust CLI &amp; Tooling on Atharva Pandey</title><link>https://atharva.page/series/rust-cli--tooling/</link><description>Recent content in Rust CLI &amp; Tooling on Atharva Pandey</description><generator>Hugo</generator><language>en-us</language><copyright>Copyright ©</copyright><lastBuildDate>Sun, 22 Sep 2024 12:10:00 +0000</lastBuildDate><atom:link href="https://atharva.page/series/rust-cli--tooling/index.xml" rel="self" type="application/rss+xml"/><item><title>Lesson 10: Testing CLI Applications — End-to-end CLI tests</title><link>https://atharva.page/post/rust/rust-cli-testing/</link><pubDate>Sun, 22 Sep 2024 12:10:00 +0000</pubDate><guid>https://atharva.page/post/rust/rust-cli-testing/</guid><description>&lt;p&gt;I shipped a CLI tool with 100% unit test coverage on the core logic. Users immediately found three bugs. The argument parser accepted &lt;code&gt;--port&lt;/code&gt; but the value wasn&amp;rsquo;t being passed to the server. The &lt;code&gt;--json&lt;/code&gt; flag produced output that wasn&amp;rsquo;t valid JSON because of a stray debug print. And &lt;code&gt;--help&lt;/code&gt; showed the wrong default for &lt;code&gt;--timeout&lt;/code&gt;. None of these bugs lived in the &amp;ldquo;core logic.&amp;rdquo; They lived in the glue between clap, the output formatter, and the actual binary. Unit tests didn&amp;rsquo;t catch them because unit tests don&amp;rsquo;t run the actual binary.&lt;/p&gt;</description></item><item><title>Lesson 9: Building TUIs with ratatui — Terminal user interfaces</title><link>https://atharva.page/post/rust/rust-cli-tui/</link><pubDate>Thu, 19 Sep 2024 07:55:00 +0000</pubDate><guid>https://atharva.page/post/rust/rust-cli-tui/</guid><description>&lt;p&gt;I was monitoring a deployment through five separate terminal windows — one for logs, one for metrics, one for the deployment status, one for the database, and one running htop. Alt-tabbing between them like a madman. Then a colleague showed me their custom TUI dashboard that combined all five views into a single terminal screen, with tabs and live-updating graphs. It was written in Rust with ratatui. I rebuilt it that weekend.&lt;/p&gt;</description></item><item><title>Lesson 8: Distribution — Static binaries, cargo-dist, Homebrew</title><link>https://atharva.page/post/rust/rust-cli-distribution/</link><pubDate>Mon, 16 Sep 2024 19:40:00 +0000</pubDate><guid>https://atharva.page/post/rust/rust-cli-distribution/</guid><description>&lt;p&gt;I built a CLI tool that three teams at work used daily. It lived in a shared directory on an NFS mount. Every time I pushed an update, I&amp;rsquo;d post in Slack: &amp;ldquo;new version in /shared/tools, please copy it to your PATH.&amp;rdquo; Half the team was running a version from three months ago because they forgot. The other half had four copies scattered across their home directories. Distribution matters. If installing your tool is harder than &lt;code&gt;brew install myapp&lt;/code&gt;, most people won&amp;rsquo;t bother.&lt;/p&gt;</description></item><item><title>Lesson 7: Cross-Compilation for Linux, Mac, Windows — Build once, run anywhere</title><link>https://atharva.page/post/rust/rust-cli-cross-compile/</link><pubDate>Sat, 14 Sep 2024 10:25:00 +0000</pubDate><guid>https://atharva.page/post/rust/rust-cli-cross-compile/</guid><description>&lt;p&gt;First time I tried to cross-compile a Rust binary from my Mac to Linux, I ran &lt;code&gt;cargo build --target x86_64-unknown-linux-gnu&lt;/code&gt; and got hit with a wall of linker errors. Missing &lt;code&gt;cc&lt;/code&gt;, wrong &lt;code&gt;libc&lt;/code&gt;, something about &lt;code&gt;crt1.o&lt;/code&gt;. It felt like the &amp;ldquo;build once, run anywhere&amp;rdquo; promise was a lie. It wasn&amp;rsquo;t — I just didn&amp;rsquo;t understand how Rust&amp;rsquo;s compilation model interacts with system libraries. Once that clicked, cross-compilation became routine.&lt;/p&gt;
&lt;h2 id="how-rust-compilation-works"&gt;How Rust Compilation Works&lt;/h2&gt;
&lt;p&gt;Rust compiles to LLVM IR, then LLVM generates machine code for the target architecture. That part works across platforms — LLVM knows how to emit x86_64, aarch64, arm, riscv, wasm, and more. The problem is the &lt;em&gt;linker&lt;/em&gt;.&lt;/p&gt;</description></item><item><title>Lesson 6: Subcommands and Complex CLI Structures — git-style interfaces</title><link>https://atharva.page/post/rust/rust-cli-subcommands/</link><pubDate>Wed, 11 Sep 2024 13:50:00 +0000</pubDate><guid>https://atharva.page/post/rust/rust-cli-subcommands/</guid><description>&lt;p&gt;Every tool starts as &lt;code&gt;mytool --flag input.txt&lt;/code&gt;. Then someone asks for a second mode. Then a third. Before you know it, you have &lt;code&gt;mytool --mode=convert --input foo --output bar&lt;/code&gt; and &lt;code&gt;mytool --mode=validate --strict --input foo&lt;/code&gt; and users are scrolling through &lt;code&gt;--help&lt;/code&gt; trying to find the three flags that matter for their use case. The answer is subcommands. &lt;code&gt;git commit&lt;/code&gt;, &lt;code&gt;docker build&lt;/code&gt;, &lt;code&gt;cargo test&lt;/code&gt; — separate commands with separate flags, unified under one binary.&lt;/p&gt;</description></item><item><title>Lesson 5: Signal Handling and Graceful Shutdown — Clean exits</title><link>https://atharva.page/post/rust/rust-cli-signals/</link><pubDate>Mon, 09 Sep 2024 09:17:00 +0000</pubDate><guid>https://atharva.page/post/rust/rust-cli-signals/</guid><description>&lt;p&gt;I had a CLI tool that converted video files. Big ones — 10, 20 gigabytes each. The conversion created a temp file, wrote the converted output there, then renamed it to the final destination. Hit Ctrl+C at the wrong moment and you&amp;rsquo;d get a half-written 15GB temp file sitting on disk. Users ran out of disk space without knowing why. All because I never handled signals properly.&lt;/p&gt;
&lt;h2 id="what-happens-when-you-press-ctrlc"&gt;What Happens When You Press Ctrl+C&lt;/h2&gt;
&lt;p&gt;When you press Ctrl+C in a terminal, the kernel sends &lt;code&gt;SIGINT&lt;/code&gt; (signal interrupt) to the foreground process group. By default, this kills your program immediately. No destructors run. No &lt;code&gt;Drop&lt;/code&gt; implementations execute. Temporary files stay on disk. Database connections aren&amp;rsquo;t closed. Partial writes aren&amp;rsquo;t rolled back.&lt;/p&gt;</description></item><item><title>Lesson 4: Colored Output and Progress Bars — UX for the terminal</title><link>https://atharva.page/post/rust/rust-cli-colored-output/</link><pubDate>Sat, 07 Sep 2024 16:33:00 +0000</pubDate><guid>https://atharva.page/post/rust/rust-cli-colored-output/</guid><description>&lt;p&gt;I used to think terminal output was either plain text or ANSI escape code soup. Then I looked at how tools like &lt;code&gt;cargo&lt;/code&gt;, &lt;code&gt;ripgrep&lt;/code&gt;, and &lt;code&gt;bat&lt;/code&gt; handle their output — color used purposefully to draw the eye, progress bars that give you actual information, spinners that tell you something is happening. Good terminal UX isn&amp;rsquo;t about making things pretty. It&amp;rsquo;s about making information scannable.&lt;/p&gt;
&lt;h2 id="raw-ansi-escape-codes"&gt;Raw ANSI Escape Codes&lt;/h2&gt;
&lt;p&gt;Before using any crate, you should understand what&amp;rsquo;s actually happening. Terminal colors are just special byte sequences embedded in the output stream:&lt;/p&gt;</description></item><item><title>Lesson 3: Configuration Files and Environment Variables — Config that scales</title><link>https://atharva.page/post/rust/rust-cli-config/</link><pubDate>Thu, 05 Sep 2024 11:08:00 +0000</pubDate><guid>https://atharva.page/post/rust/rust-cli-config/</guid><description>&lt;p&gt;I shipped a CLI tool with 23 flags once. Twenty-three. The &lt;code&gt;--help&lt;/code&gt; output scrolled past two terminal screens. Users hated it, nobody could remember the flags, and every deployment script was a wall of backslash-continued command lines. That&amp;rsquo;s when I learned: if your tool has more than about eight flags, you need a configuration file.&lt;/p&gt;
&lt;h2 id="the-configuration-hierarchy"&gt;The Configuration Hierarchy&lt;/h2&gt;
&lt;p&gt;Every serious CLI tool follows the same precedence order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Command-line flags (highest priority)&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;Project-local config file (&lt;code&gt;.myapp.toml&lt;/code&gt; in the current directory)&lt;/li&gt;
&lt;li&gt;User config file (&lt;code&gt;~/.config/myapp/config.toml&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;System config file (&lt;code&gt;/etc/myapp/config.toml&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Compiled-in defaults (lowest priority)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each layer overrides the one below it. This lets users set defaults in their home directory, override them per-project, and override those on a per-invocation basis with flags. kubectl, git, docker — they all work this way.&lt;/p&gt;</description></item><item><title>Lesson 2: stdin, stdout, stderr — I/O patterns</title><link>https://atharva.page/post/rust/rust-cli-io/</link><pubDate>Tue, 03 Sep 2024 08:45:00 +0000</pubDate><guid>https://atharva.page/post/rust/rust-cli-io/</guid><description>&lt;p&gt;A coworker once asked me to review a Rust CLI they&amp;rsquo;d written. It read a CSV file, transformed some columns, and wrote the result. Worked perfectly — until someone piped in a 2GB file. The tool ate 4GB of RAM, hung for thirty seconds, then crashed. They were reading the entire file into a &lt;code&gt;String&lt;/code&gt; before processing a single line. Classic.&lt;/p&gt;
&lt;h2 id="why-io-is-harder-than-it-looks"&gt;Why I/O Is Harder Than It Looks&lt;/h2&gt;
&lt;p&gt;Unix tools work because of a simple contract: read from stdin, write to stdout, errors to stderr. &lt;code&gt;cat file | grep pattern | sort | uniq -c&lt;/code&gt;. Each program does one thing. They compose through pipes. It&amp;rsquo;s beautiful when it works.&lt;/p&gt;</description></item><item><title>Lesson 1: clap — Argument parsing done right</title><link>https://atharva.page/post/rust/rust-cli-clap/</link><pubDate>Sun, 01 Sep 2024 14:22:00 +0000</pubDate><guid>https://atharva.page/post/rust/rust-cli-clap/</guid><description>&lt;p&gt;I&amp;rsquo;ve written argument parsers by hand in four different languages. Every single time, I ended up with a tangled mess of string matching, edge cases around flags that take optional values, and help text that drifted out of sync with reality within a week. Then I found clap.&lt;/p&gt;
&lt;h2 id="the-problem-with-rolling-your-own"&gt;The Problem With Rolling Your Own&lt;/h2&gt;
&lt;p&gt;Parsing command-line arguments &lt;em&gt;seems&lt;/em&gt; simple. You grab &lt;code&gt;std::env::args()&lt;/code&gt;, maybe split on &lt;code&gt;=&lt;/code&gt;, handle some &lt;code&gt;--flag&lt;/code&gt; and &lt;code&gt;-f&lt;/code&gt; cases. Works great until someone passes &lt;code&gt;--output=&lt;/code&gt; with no value. Or uses &lt;code&gt;-vvv&lt;/code&gt; for triple verbosity. Or expects &lt;code&gt;--help&lt;/code&gt; to just work. Or wants &lt;code&gt;--color=always|never|auto&lt;/code&gt;. Suddenly your 40-line parser is 400 lines of spaghetti.&lt;/p&gt;</description></item></channel></rss>