The original C-version of the program "sl" provides the ability for the user to add command-line options to control things such as which train to display, or to run in "Accident" mode. We also want to add this to our Rust version. You may recall Using Clap to Parse Rust Arguments earlier to get command-line arguments. We'll implement that now in our program.
Add Clap (Command-Line Argument Parser) Dependency to the Project
Below is what your "main.rs" file should look like currently:
The image model is not going to be the only thing we might get from the command-line. There will likely be quite a bit of processing to make sure we have good data, so let's put this stuff in its own function, in its own module/file. As a starter, create the following file:
Although not exactly accurate, you can think of the "get_options.rs" file as being one level beneath the "main.rs" file, and you can think of the "main.rs" file as being the same thing as "crate". This is why the "use" statement can't just point to "images"; it needs to start one level up, at the "main", or "crate", level, and then follow the path ("::") to "images".
The "images" module was defined in "main.rs", as a module attached to the "sl" project as a whole, so we don't declare it here in "get_options.rs".
Also, since a &str-type of variable "dies" when program-control leaves the function in which it is used, we need to return the value of our const-type "PLANE" value as something more long-lived than a &-str-based const; that's why we convert it to a String-type, and then return that String.
And modify "main()" as needed:
We're no longer putting our image data in a &str-type of variable, but rather into a String-type of value, because that's what's coming back from the "get_opts()" function. Although not necessary, changing the name of the variable provides a little documentation about what's happening. But when we send it to the "str_to_vecvecstring()" function, we send that with an & pre-fixed, to send a borrowed slice, because a slice is what that function expects.
To use clap, we need to create a struct-type of data definition. Then we ned to create a variable of that type, and fill that variable with arguments provided by clap's parser() function. Then we match that value to the available possibilities, and return the match, if found, otherwise the default of "D51" if not found. Here's the code:
We could have put the "use clap..." statement at the top of the file, outside of the function "get_options()", which would make it global to the file (which doesn't have anything in it but this function, so it's a minor difference), but it's not needed by anything outside of the function, so we put it in the function to keep its scope as localized as possible.
We also don't need the "use crate::images..." line to be global to the file, so we've moved it into the function also.
Because the "clap" crate is separate from the project crate, we don't need to start the "use clap..." line with the top-level "crate" for the "sl" project, like we did for the "use crate..." statement. We instead start at the top-level of the "clap" crate.
We then define the "Arguments"-data type to be a struct, which currently only has one data item, the "model" field, which will hold a String. At the command-line, we can enter a short form ("p" for "plane", etc) or the long form ("plane"), and if that option is not defined on the command-line, the program will default to setting that value to "D51".
We then create a variable named "args" that is of the "Argument"-type, which gets assigned by the clap parser.
Then we use a "match" statement to compare the incoming (uppercased) value in "model" to various permutations of the possible models. This match statement then returns the appropriate const which gets assigned to "image_str".
And finally that value is converted to a String data type and returned from the "get_opts()" function.
So you should be able to run the program in ways like the following:
$ cargo run
$ cargo run -- --model=p
$ cargo run -- -mb
$ cargo run -- --model c51
$ cargo run -- -m CyClE
$ cargo run -- --help
Notice that we did not include the COALCAR image; that's because it's not designed to travel across the screen without being pulled by one of the other craft. But if you want to include it in your list, feel free to do so. But be aware that you can not use "C" as a shortcut for "COALCAR", because "C" is already being used by the "C51". (Well, you could take it from the "C51" and use it for the "COALCAR" instead, if you wanted.)
When we have the image actually animated, going across the screen, we'll want to give the option to have the image "fly". On the command-line, a simple "--fly" option will activate this. If the option is not given, the default will be to not fly. Here's how the clap portion of the program looks, to do this:
Not only have we added the capability to specify the "fly" option, we're not having to return that value from the function. Accordingly, we have to make some modifications to the function's definition and return statement, as well as to the calling routine:
Give it a try:
$ cargo run
$ cargo run -- --model=p
$ cargo run -- --fly
$ cargo run -- --model c51 -f
$ cargo run -- -m CyClE --fly
$ cargo run -- --help
Of course, we're not animated yet, so we won't actually fly, but you should see a message on screen telling you the state of the "fly" option.
Now instead of the "get_opts()" function returning a string composed of the model image, it returns that string, and a boolean value of "true" or "false". We put them both into a "shopping bag", and send that "shopping bag" back to the calling routine. A "shopping bag" like this, that holds multiple variables, is called a "tuple". But just to be confusing, technically, this is not a tuple. And even if it was, it should be noted that a "tuple" has nothing to do with "two", even though this contains two values.
A true tuple would have a name on the outer shopping bag, and would not have names for the inner values. Instead, the inner values would be referenced by an index number that corresponds to the order in which the values were placed into the bag. For example, here's a real tuple with our same values:
let options: (String, boolean = ("<\")))><".to_string(), false);
println("The picture of the model = {}", options.0);
println!("And the boolean value of 'fly' = {}.", options.1);
"options" is the name of this tuple "shopping bag", and its first-indexed value is a String variable, and its second-indexed value is a boolean variable.
A "shopping bag" that has a name on the outside, and a name for each individually-labeled item within it which are not accessed according to order, is called a "struct". We've been using a struct for our clap-related code.
The last option available in the C-version of "sl" is the option to interrupt the program with a break signal (Ctrl-C for most PC users). Again, it's as easy to add the option as it is the other options:
The last argument we want to add is an option to control the speed of the animation. This option is not in the original C-version of "sl", but it seems handy enough to include it. You should know the process by now.
When we compile this program, we get lots of warnings abour unused variables. Whereas it's good to get these warnings, we are intentionally not using the variables at the moment. There are several things we could do to deal with these warnings, including doing nothing, and just living with them, but an easy way to turn them off temporarily is to, at the top of the "main.rs" file, before any other code, issue a compiler directive to allow unused items. There are several variants even of this option, but the simplest that probably works the best for us, is to do this, which will turn off the warnings project-wide:
Once we have these options in use, we'll want to remove this directive, so that we'll start getting warnings again, which are usually beneficial.