Skip to content

Utils.rs

Command Line Argument Parsing (clap)

In this file, we define a clap Args struct.

use std::path::PathBuf;
use clap::Parser;
use crate::utils::version;
#[derive(Parser, Debug)]
#[command(author, version = version(), about)]
pub struct Cli {
#[arg(short, long, value_name = "FLOAT", help = "Tick rate, i.e. number of ticks per second", default_value_t = 1.0)]
pub tick_rate: f64,
#[arg(
short,
long,
value_name = "FLOAT",
help = "Frame rate, i.e. number of frames per second",
default_value_t = 60.0
)]
pub frame_rate: f64,
}

This allows us to pass command line arguments to our terminal user interface if we need to.

In addition to command line arguments, we typically want the version of the command line program to show up on request. In the clap command, we pass in an argument called version(). This version() function (defined in src/utils.rs) uses a environment variable called RATATUI_ASYNC_TEMPLATE_GIT_INFO to get the version number with the git commit hash. RATATUI_ASYNC_TEMPLATE_GIT_INFO is populated in ./build.rs when building with cargo, because of this line:

println!("cargo:rustc-env=RATATUI_ASYNC_TEMPLATE_GIT_INFO={}", git_describe);

You can configure what the version string should look like by modifying the string template code in utils::version().

XDG Base Directory Specification

Most command line tools have configuration files or data files that they need to store somewhere. To be a good citizen, you might want to consider following the XDG Base Directory Specification.

This template uses directories-rs and ProjectDirs’s config and data local directories. You can find more information about the exact location for your operating system here: https://github.com/dirs-dev/directories-rs#projectdirs.

This template also prints out the location when you pass in the --version command line argument.

There are situations where you or your users may want to override where the configuration and data files should be located. This can be accomplished by using the environment variables RATATUI_ASYNC_TEMPLATE_CONFIG and RATATUI_ASYNC_TEMPLATE_DATA.

The functions that calculate the config and data directories are in src/utils.rs. Feel free to modify the utils::get_config_dir() and utils::get_data_dir() functions as you see fit.

Logging

The utils::initialize_logging() function is defined in src/utils.rs. The log level is decided by the RUST_LOG environment variable (default = log::LevelFilter::Info). In addition, the location of the log files are decided by the RATATUI_ASYNC_TEMPLATE_DATA environment variable (default = XDG_DATA_HOME (local)).

I tend to use .envrc and direnv for development purposes, and I have the following in my .envrc:

Terminal window
export RATATUI_COUNTER_CONFIG=`pwd`/.config
export RATATUI_COUNTER_DATA=`pwd`/.data
export RATATUI_COUNTER_LOG_LEVEL=debug

This puts the log files in the RATATUI_ASYNC_TEMPLATE_DATA folder, i.e. .data folder in the current directory, and sets the log level to RUST_LOG, i.e. debug when I am prototyping and developing using cargo run.

Top half is a iTerm2 terminal with the TUI showing a Vertical split with tui-logger widget. Bottom half is a ITerm2 terminal showing the output of running tail -f on the log file.

Using the RATATUI_ASYNC_TEMPLATE_CONFIG environment variable also allows me to have configuration data that I can use for testing when development that doesn’t affect my local user configuration for the same program.

Panic Handler

Finally, let’s discuss the initialize_panic_handler() function, which is also defined in src/utils.rs, and is used to define a callback when the application panics. Your application may panic for a number of reasons (e.g. when you call .unwrap() on a None). And when this happens, you want to be a good citizen and:

  1. provide a useful stacktrace so that they can report errors back to you.
  2. not leave the users terminal state in a botched condition, resetting it back to the way it was.

In the screenshot below, I added a None.unwrap() into a function that is called on a keypress, so that you can see what a prettier backtrace looks like:

utils::initialize_panic_handler() also calls Tui::new().exit() to reset the terminal state back to the way it was before the user started the TUI program. We’ll learn more about the Tui in the next section.