envfmt: Overview

Overview

A quick intro to envfmt, what it does, and why I built it.

I built envfmt while working on cargo-eth, my personal command-line tool for ethereum development. I needed a simple way to expand environment variables in config strings, like for an RPC URL:

eth.toml
[fork]
rpc_url="https://eth-mainnet.g.alchemy.com/v2/$ALCHEMY_API_KEY"

I looked for a crate that could do this, but everything I found felt too complex for what I needed. So, I decided to just write my own.

So, what is envfmt? It’s a small Rust crate that expands environment variables in strings, just like how a shell does it. It’s lightweight, has no dependencies, and is pretty easy to use.

It supports the basic stuff you’d expect:

  • Simple variables: $VAR
  • Braced variables: ${VAR}
  • Default values for unset variables: ${VAR:-default}
  • Escaping a dollar sign: $$

Here’s a quick example of how it works. Imagine this code in your main.rs:

src/main.rs
fn main() {
    let rpc_url_config = "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}";
    let rpc_url = envfmt::format(rpc_url_config).unwrap();
    println!("{}", rpc_url);
}

If you run this with the ALCHEMY_API_KEY set in your environment:

Shell
ALCHEMY_API_KEY=your-key-goes-here cargo run

The output will be the expanded URL:

Shell
https://eth-mainnet.g.alchemy.com/v2/your-key-goes-here

How It Works

There are two main functions you’ll use:

  1. envfmt::format(): This one is for when your variables are in the process environment. It’s the simplest way to get started.
  2. envfmt::format_with():This is the more flexible one. You can give it any data source you want, like a HashMap. This is great if your variables are not in the environment, maybe they come from a config file or some other part of your program. It basically turns envfmt into a lightweight template engine.

The main idea was to make it flexible. That’s why I created the Context trait. Anything that implements this trait can be a source for your variables. A HashMap works out of the box, but you could also make your own struct provide the data. I wanted to keep it small and focused on doing just one thing well.