30 Days of Rust - Biting off more than I can chew
Tags: rust, learning, cli
Published: 2023-05-02 20:51:21

Today is the first day of the CLI project outline in my previous article. My optimism was high yesterday, but as I sat down today to write my first lines of code, I found myself struggling to work out what to do. I've taken a step back and want to first try and scope my first features for the tool.

Yesterday we identified two overarching goals or epics for the project:
>1. CLI installation and arg parsing;
>2. Process management and meta data collection.

I'm now going to drilldown on objective 2 to try and find a bit sized task to tackle today.

## Where do I begin??
Our CLI tool is going to need a name. Now I already found that my chosen project name had already been taken on crates (😡)  so I opted for `time-rs`.

Now that we have a name we can take a look for some templates. I found a [solid project template](https://github.com/rust-github/template) on Github. My only gripe so far is that it doesn't include any `pre-commit` hooks of the box.

I've selected my licence and deleted a bunch of superfluous contribution guideline stuff. With the easy bits done, now let's start thinking about code.

##  What do we need from our processes and why?
### Compatibility
The aim of this CLI tool is to be able to time the duration of an arbitrary command run on an operating system. This leads to some pretty obvious characteristics of the tool:
1. To run an arbitrary command, it must function platform-agnostically
2. Whatever structured response it yields, it must be the same across all platforms

To make ground on these issues, we'll need to ensure that we can run our unit tests in different environments. Fortunately, the template I used includes this in the Github Action out of the box. That said, until we have a proper CLI package, we'll need to test our code using some kind of OS based parameterisation.

### Consistent response data
Providing a useful and consistent response will be key to making the tool easy to use for developers:
1. Ideally, the time measurement should be as accurate as possible. This may entail subtracting the difference from opening and closing a process. Perhaps this also indicates that any timer should be located close in the call stack to where the process is opened.
2. Most commands will also leverage stdout and stderr (let's not worry about stdin right now). This will probably be of use for the user. But it may make presenting our timed output more challenging. Perhaps we need to differentiate somehow.
3. Do we care about making our response machine parsable? This would allow our users to make automated decisions based on the results.

Many of these attributes are too big to tackle in one go, but I think we're making some progress filtering down to a task level.

## So many features, such little time
Before we even can ask the question of how accurate `time-rs` is, we need to be able to open processes in Rust. It also needs to be able to retrieve the `stdout` and `stderr` of a process. We may not always want to feed it up to our user, but having it available to us will be useful.

Formulated as concrete tasks:
- [ ] `time-rs` has a function that can launch a process with any command
- [ ] We can read and test the `stdout` and `stderr` of a command

### std::process or extern crate subprocess?
Now we have two clear tasks, we have to decide what tools to use to solve the problem. One of my goals with this tool was to learn the standard library better. So let's take a look at [`std::process`](https://doc.rust-lang.org/std/process/index.html).

The module coalesces around the `Command` struct. In chaining method calls, we can easily setup a child process and even handle errors inline:
```rust
use std::process::Command;

let output = Command::new("echo")
                     .arg("Hello world")
                     .output()
                     .expect("Failed to execute command");

assert_eq!(b"Hello world\n", output.stdout.as_slice());
```

One issue I'm seeing with this API is the `.arg` method. How can I dynamically generate method calls at runtime? Is the process only run when I call the `.output` or `.spawn` method? Also, in order to chain calls, I'll need to be able to determine what arguments and flags have been specified by the user.

This could be one advantage of using the [subprocess](https://docs.rs/subprocess/latest/subprocess/index.html) third-party library. It contains the `Exec::shell` which allows you to pass `&str` commands which are then used to build a `Popen` instance. Interestingly, the authors of the library base the library's design on Python's standard library `subprocess` module.

Though this might be a convenient solution, a quick google revealed to me the `clap` CLI project (mentioned actually in the [official docs](https://rust-cli.github.io/book/tutorial/cli-args.html#parsing-cli-arguments-with-clap)). I think this will provide the [functionality needed](https://docs.rs/clap/latest/clap/trait.Parser.html#method.parse_from)to derive arguments from our users' commands such that we won't need to rely on `subprocess`. Also there has been little activity on the `subprocess` github repo, which makes me feel uncertain about selecting it as a tool.

So with those questions cleared up, I'll start writing code tomorrow 💪