Friday, May 05, 2023

A Simple Introduction to CLAP in the Rust Programming Language

"CLAP" stands for Command-Line Argument Parser.

In order to get started using clap, you need to understand three things:

1 - How to Run a Simple Rust Program

Suppose you want to create a program called "fun". You should have a working installation of Rust and Cargo (the web is full of explanations of how to install this). In your file directory where you want to create the "fun" project:

$ cargo new fun

$ cargo run

This should print "Hello, World!", because every cargo-created project has this simple capability as a starting point.

2 - How to Work with a struct

This is a little bit more advanced. Here's the "src/main.rs" program from our "cargo new fun" command above:

src/main.rs
fn main()
	println!("Hello, world!");
}

A structure is simply a variable of a customized type, which often holds other variables. You should be familiar in a general way with variables, and know that they come in various kinds, or types: there are String variables and there are i32 variables and there are usize variables, etc. A struct simply allows you to create your own customized variable type, and fill it with a collection of other variables of various types.

Say you have a variable called "last" which holds a person's last name, and a variable called "age" which holds that person's age in years. The first variable might be a type of String (which holds a string of text), and the second might be a type of i8 (which holds an integer in the range of -128 to +127; a u8, which holds a range of 0 to 255, might be a better option).

Think of these two variables as two different types of fruit. We can carry them around in our hands if we like, or we can create a shopping bag to put them into. This shopping bag is analogous to a struct, except that a struct is well-defined as to what it can hold, whereas a shopping bag will hold just about anything pretty much, willy-nilly.

So a better way to think of it is as a custom-tool case, with exact-fitting compartments for the tools.

Example:

// This is the "master design" from which all toolboxes will be built.
struct toolbox_template {
  owner: String, // It will belong to a specific person,
  hammer: String, // and will hold a hammer,
  screwdriver: i8, // and a screwdriver of a certain size.
}

Note that this is just the definition of the "template" for a toolbox; it doesn't actually create a toolbox. Let's put this template into our "main.rs" file, along with the creation of two toolboxes based on this template, and then we'll print out a couple of messages about those tools. (We no longer need the "Hello, World!" println, so we'll delete it as per the Strikethru marking.)

src/main.rs
struct ToolboxTemplate {
  owner: String,
  hammer: String,
  screwdriver: i8,
}

fn main() {
  println!("Hello, world!");

  // Let's build a custom toolbox for Him.
  let his_box: ToolboxTemplate = ToolboxTemplate {
    owner: "Joe".to_string(),
    hammer: "sledge".to_string(),
    screwdriver: 8,
  };
  
  // And one for Her.
  let her_box: ToolboxTemplate = ToolboxTemplate {
    owner: "Jane".to_string(),
    hammer: "pink-handled".to_string(),
    screwdriver: 4
  };

  // Print the details of his toolbox.
  println!("{} has a {} hammer and a Number {} screwdriver.",
	his_box.owner, his_box.hammer, his_box.screwdriver
  );

  // And of hers.
  println!("{} has a {} hammer and a Number {} screwdriver.",
    her_box.owner, her_box.hammer, her_box.screwdriver
  );
} // end of main()

So you can see that a struct is just a custom-built variable, a "carrying case", that holds various other variables. The "struct" part defines the type, and then you have to create variables of that type which actually hold the desired data.

A Basic clap Setup

So, you can write a simple Rust program, and you kindda understand a struct. Good. Now lets add clap into the mix.

We first have to tell Cargo (the Rust "compiler" (sort of, but not really)) about Clap. This is done by adding some information to the "Cargo.toml" file. Before doing this, my Cargo.toml file for the "fun" program looks like this:

$ cat Cargo.toml 
[package]
name = "fun"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
$

If you have a recent version of Clap/Cargo, you can add Clap to your "fun" project with this command:

$ cargo add clap --features derive

That'll generate some churn, after which your "Cargo.toml" file will look more like this (the new stuff is in hilite):

$ cat Cargo.toml 
[package]
name = "fun"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "4.2.7", features = ["derive"] }

Now Cargo knows about clap. Your program does not, but Cargo does. When you compile your program again, Cargo will include all the clap stuff. Try it. Your program will run just as it did before, except for all the extra compiling of Clap.

We want to be able to specify our hammer and screwdriver on the command-line. The command will look something like this:

$ code run -- --name=Bob --hammer=peen --screwdriver=9

Add the hi-lighted code below, and delete the code that is in Strikethru. The "use" line simply tells our program the path to find parse-related stuff in the Clap crate. The "derive" line magically makes our struct definition able to associate its components with information coming from the Command-Line Argument Parser (clap).

src/main.rs
use clap::Parser;

#[derive(Parser)]
struct ToolboxTemplate {
  owner: String,
  hammer: String,
  screwdriver: i8,
}

fn main() {

  // Let's build a custom toolbox for Him.
  let his_box: ToolboxTemplate = ToolboxTemplate {
    owner: "Joe".to_string(),
    hammer: "sledge".to_string(),
    screwdriver: 8,
  };
  
  // And one for Her.
  let her_box: ToolboxTemplate = ToolboxTemplate {
    owner: "Jane".to_string(),
    hammer: "pink-handled".to_string(),
    screwdriver: 4
  };
  
  println!("{} has a {} hammer and a Number {} screwdriver.",
	his_box.owner, his_box.hammer, his_box.screwdriver
  );
  
  // Print the details of his toolbox.
  println!("{} has a {} hammer and a Number {} screwdriver.",
	his_box.owner, his_box.hammer, his_box.screwdriver
  );

  
  // Let's build a toolbox based on command-line arguments.
  let toolbox: ToolboxTemplate = ToolboxTemplate::parse();

  // And of hers.
  // And then print out the details of that toolbox.
  println!("{} has a {} hammer and a Number {} screwdriver.",
    her_box.owner, her_box.hammer, her_box.screwdriver
    toolbox.owner, toolbox.hammer, toolbox.screwdriver
  );
} // end of main()

If you try to run this with arguments, like so:

$ cargo run -- --name=Bob hammer=peen screwdriver=9

you'll get a message about an unexpected argument, and a tip that tells you to do what you're already doing, along with a "usage" blurb.

However, if you run your program this way:

$ cargo run -- --Bob peen 9

it works!

And that's all that's needed for a very basic clap setup. It's reading your command-line arguments according to the position they're given. If you change the order, like this:

$ cargo run -- --9 Bob peen

you'll break your program. Like this, though:

$ cargo run -- --peen Bob 9

and you'll just get unwanted results.

So we want to be able to name our arguments. That's done like this:

src/main.rs
use clap::Parser;

#[derive(Parser)]
struct ToolboxTemplate {
  #[arg(long)]
  owner: String,
  #[arg(long)]
  hammer: String,
  #[arg(long)]
  screwdriver: i8,
};
...

Try running that with $ cargo run -- --name=Bob hammer=peen screwdriver=9.

Hmm, a different error message. And again, it doesn't really make sense. Ah, but now I see it. We defined an argument named "owner", but we're typing in an argument named "name". Let's try this:

$ cargo run -- --ownername=Bob hammer=peen screwdriver=9.

Yay! That works!

But what if we really want to type in "name" instead of "owner", but don't want to change the variable name? Easy. Just do this:

src/main.rs
use clap::Parser;

#[derive(Parser)]
struct ToolboxTemplate {
  #[arg(long="name")]
  owner: String,
  #[arg(long)]
  hammer: String,
  #[arg(long)]
  screwdriver: i8,
};
...

What if we want to just use "s" for "screwdriver"?

src/main.rs
use clap::Parser;

#[derive(Parser)]
struct ToolboxTemplate {
  #[arg(long="name")]
  owner: String,
  #[arg(long)]
  hammer: String,
  #[arg(longshort)]
  screwdriver: i8,
};
...

Then our command-line would like look: cargo run -- --name=Bob --hammer=peen -s=9 Notice that single-letter arguments are introduced with a single-hyphen (e.g. -s=9), rather than a double-hyphen (--s=9).

What if we want to just use "d" for "screwDriver"?

src/main.rs
use clap::Parser;

#[derive(Parser)]
struct ToolboxTemplate {
  #[arg(long="name")]
  owner: String,
  #[arg(long)]
  hammer: String,
  #[arg(short='d')]
  screwdriver: i8,
};
...

Notice that the definition uses single-quotes around "d" rather than double-quotes.

What if we want to allow either a short form or a long form?

src/main.rs
use clap::Parser;

#[derive(Parser)]
struct ToolboxTemplate {
  #[arg(short,long="name")]
  owner: String,
  
  #[arg(short,long)]
  hammer: String,
    
  #[arg(short='d',long)]
  screwdriver: i8,
};
...

What if we want to make the name optional, with a default?

src/main.rs
use clap::Parser;

#[derive(Parser)]
struct ToolboxTemplate {
  #[arg(short,long="name",default_value_t=String::from("Bubba"))]
  owner: String,
 
  #[arg(short,long)]
  hammer: String,

  #[arg(short,long)]
  screwdriver: i8,
};
...

Note that "Bubba".to_string() won't work in this case, so we had to use an alternative method of converting a string literal to a String-type. Don't worry for now about understanding that; just know that usually it doesn't matter which conversion method you use, and that if one method doesn't work, try another.

This short tutorial won't answer all your questions, but it should get you started. Have fun, Rustacean!

OCR On Your Smart Phone

Did you know that your phone can possibly do Optical Character Recognition (OCR)?

Mine, a Samsung Galaxy S22 Ultra, can.

  • I took a picture of an informative display hanging on the wall, that had two columns of side-by-side text.
The image hanging on the wall.
The image
  • I then used the camera's photo editing software (the pen icon at the bottom of the image below) to crop the image to just one of the text columns, and saved (upper-right corner of the cropped image below) that image.
The image in my phone's Gallery.
Cropping the image to just one of the columns.
  • Then at the bottom right corner of that same editing window is a little yellow "T" in a broken-outline box (see the Gallery image above). When I clicked on that "T", it OCR'd the text and highlighted it.
  • I was then able to single-press on the text, which popped up a menu allowing me to "Select All", which popped up another menu allowing me to "Copy".
  • I could then go to an editor of some sort, and "Paste" the text into the editor.
  • I then went back to my image, and edited it again, and "Revert"ed it back to the original.
  • I then repeated the process for the second column.

In just a minute or two, I had the full text of the two columns of the informative display in an editor. With a clean original image with clean-looking text, the accuracy is very high.

Finished text.

The first unit of the hospital was erected in September 1924, at a cost of $150,000. West Texas Baptist Sanitarium had five stories, 72 rooms and admitted more than 800 patients during the first year.

When it opened, West Texas Baptist Sanitarium touted: hot and cold running water in each room; excellent nursing services; three modern elevators; three well-equipped operating rooms; capable physicians and surgeons; and an obstetrical department.

Labor and delivery services were quickly utilized. The first baby was born at Hendrick less than one month after the doors opened. Pauline Marie Turnidge, daughter of Mr. and Mrs. W.A. Turnidge, was born on October 17, 1924.

The vision of a hospital for the Texas Midwest was well under way as the vision of Reverend Millard Jenkins became a reality. The motto for West Texas Baptist Sanitarium was that it opened its doors to everyone, "no matter what your belief or creed."