Anatomy of a Rust Program, Part VII: /src/wsl/inv.rs and /src/wsl/inv/*.rs

This blog post is the seventh in a series that describes the structure of the wink command line program that I have written in rust and that I use to invoke other Windows and Linux programs from bash and Windows shells under Windows Subsystem for Linux. This post describes several types used by the wink program to contain metadata about commands that Windows can invoke and to invoke such commands. This blog post describes several files that appear as the crate::wsl::inv path and its child paths such as crate::wsl::inv::invocable.

In no particular order, this is one of the files.

An Invocable represents metadata about a command that Windows can invoke as an executable, with cmd.exe, with explorer.exe, or with WSL bash.exe, where cmd.exe supports child commands such as call and start, neither of which seems to work reliability as intended, such as to start a Windows binary as a background process.

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]

Invocables can be serialized to and deserialized from JSON, and support some cloning and debugging features.

The implementation includes the cmp(), partial(), and eq() functions required for sorting Invocables by their command codes.

The base() function returns an Invocable with default values applied. The bin_with(), bin(), exp_with(), exp(), bkg_with(), bkg(), cmd_with(), cmd(), sh_with(), and sh() functions create Invokers with different properties to invoke commands in various ways allowed by Windows, which may use (named _with) or not use command line parameters.

 use crate::wsl::inv::invocable::Invocable; 

InvocableCategory depends on Invocable.

#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)]

InvocableCategories can be serialized to and deserialized from JSON, and support some cloning and debugging features.

The implementation includes the cmp(), partial(), and eq() functions required for sorting InvocableCategories by their names.

The new() function works as a constructor.

The add() function adds an Invocable to an InvocableCategory.

The remaining functions add a number of Invocables to an InvocableCategory.

use crate::wsl::get_config_file_path;
use crate::wsl::inv::invocable::Invocable;
use crate::wsl::inv::invocablecategory::InvocableCategory;

InvocableCategoryList has some dependencies.

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]

InvocableCategoryList supports some features.

The get() function constructs the default CategoryList by creating a number of InvocableCategories with hard-coded names and calling corresponding methods before adding them to this InvocableCategoryList. I intend to refactor this to use delegates. This includes some logic to add additional InvocableCategories and Invocables from a JSON file to this InvocableCategoryList, which likely needs some corrections to resolve potential conflicts in category and command code names.

The get_invocable function uses some rust constructs to avoid nested for loops. For each category, in its list of Invocables, find the Invocable with the command_code value that matches the command_code argument. The pipes indicate delegates.

impl InvocableCategoryList {
    pub fn get_invocable(&self, command_code: &str) -> Option<&Invocable> {
        self.categories
            .iter()
            .flat_map(|c| c.invocables.iter())
            .find(|i| i.command_code == command_code)
    }
}

This fragment is interesting.

        if std::path::Path::new(&path).exists() {
            // if the path exists, then propagate all errors, so OK to unwrap from here
            let data = std::fs::read_to_string(&path)
                .unwrap_or_else(|_| panic!("Unable to read {0}", &path));
            let deserialized: InvocableCategoryList = serde_json::from_str(&data).unwrap();

If the file at the specified path exists, then panic if it cannot be read; otherwise, create an InvocableCategoryList from its contents. Underscore is a construct that is something like a default everything.

This is struct has no fields but defines the invoke() function that uses an Invocable and some parameters from the command line to invoke a process using std::process::Command. The code seems relatively self-explanatory.

  • /src/wsl/inv.rs: Groups the types defined in the files under the /src/wsl/inv directory under the wsl::inv path.
pub mod invocable;
pub mod invocablecategory;
pub mod invocablecategorylist;
pub mod invoker;

If I understand correctly, this makes the child paths available in this parent path, basically causing the compiler to include those files or allow other files to reference the types that they contain through the wsl::inv path.

2 thoughts on “Anatomy of a Rust Program, Part VII: /src/wsl/inv.rs and /src/wsl/inv/*.rs

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: